简体   繁体   English

按下按钮时更改锚点/约束

[英]Change anchors/Constraints when a button is pressed

I have added a subview inside of a UIViewController. 我在UIViewController中添加了一个子视图。 This subview (called PlayerView) should have two states. 该子视图(称为PlayerView)应具有两种状态。 One is a collapsed state, which only has a height of 56. It's expanded state has a height of 385. When the subview is changed from collapsed to expanded, I want the subviews within the PlayerView to change shape and position. 一种是折叠状态,其高度仅为56。其展开状态的高度为385。当子视图从折叠状态变为展开状态时,我希望PlayerView中的子视图改变形状和位置。

When the PlayerView is added to the UIViewController (MasterViewController), it uses layoutSubviews() like so: 将PlayerView添加到UIViewController(MasterViewController)时,它使用layoutSubviews()如下:

override func layoutSubviews() {
    super.layoutSubviews()

    backgroundColor = UIColor(red: 24/255, green: 24/255, blue: 24/255, alpha: 1)

    roundCorners([.topLeft, .topRight], radius: 10)


    addSubview(albumImage)
    addSubview(songTitleLabel)
    addSubview(songArtistLabel)
    addSubview(playPauseButton)

    _ = albumImage.anchor(nil, left: leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 20, bottomConstant: 0, rightConstant: 0, widthConstant: 40, heightConstant: 40)
    albumImage.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    _ = playPauseButton.anchor(nil, left: nil, bottom: nil, right: rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 30, widthConstant: 22, heightConstant: 22)
    _ = songTitleLabel.anchor(albumImage.topAnchor, left: albumImage.rightAnchor, bottom: nil, right: playPauseButton.leftAnchor, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: songTitleLabel.frame.width, heightConstant: songTitleLabel.frame.height)
    _ = songArtistLabel.anchor(songTitleLabel.bottomAnchor, left: songTitleLabel.leftAnchor, bottom: nil, right: playPauseButton.leftAnchor, topConstant: 2, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: songArtistLabel.frame.width, heightConstant: songArtistLabel.frame.height)
    playPauseButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true

}

When the collapsed view is pressed, it is supposed to expand. 当按下折叠视图时,它应该展开。 Here is how I went about doing that (This is inside my MasterViewController): 这是我要做的事情(这在我的MasterViewController内部):

let pv = PlayerView()

func expandPlayerView() {
    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {

        self.view.layoutIfNeeded()
        self.pv.frame = CGRect(x: 0, y: CGFloat((self.view.frame.maxY) - 352), width: 375, height: 350)
        self.pv.changePlayerView()

        self.view.layoutSubviews()
    }) { (true) in

    }
}

The changePlayerView() function is defined like this: changePlayerView()函数的定义如下:

func changePlayerView() {
    subviews.forEach({$0.removeFromSuperview()})
    addSubview(playPauseButton)
    playPauseButton.removeConstraints(playPauseButton.constraints)        

    _ = playPauseButton.anchor(nil, left: nil, bottom: bottomAnchor, right: nil, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 22, heightConstant: 22)
    playPauseButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true        
}

What happens after all of this code is executed is: 1. The playerView expands to 385 fine 2. All other subviews are removed from the playerView 3. The play pause button takes up almost the entire PlayerView subview, and does not conform to its width and height constraint of 22 执行完所有这些代码后,将发生以下情况:1. playerView扩展为385 fine 2.从playerView中删除了所有其他子视图3.播放暂停按钮几乎占据了整个PlayerView子视图,并且不符合其宽度和高度限制为22

What do I need to do to get the playPauseButton conform to all of its constraints? 我需要怎么做才能使playPauseButton符合其所有约束?

EDIT: 编辑:

The .anchor function is defined in an extension of UIView like so: .anchor函数在UIView的扩展中定义,如下所示:

func anchor(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
    translatesAutoresizingMaskIntoConstraints = false

    var anchors = [NSLayoutConstraint]()

    if let top = top{
        anchors.append(topAnchor.constraint(equalTo: top, constant: topConstant))
    }
    if let left = left{
        anchors.append(leftAnchor.constraint(equalTo: left, constant: leftConstant))
    }
    if let bottom = bottom{
        anchors.append(bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant))
    }
    if let right = right{
        anchors.append(rightAnchor.constraint(equalTo: right, constant: -rightConstant))
    }
    if widthConstant > 0{
        anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
    }
    if heightConstant > 0{
        anchors.append(heightAnchor.constraint(equalToConstant: heightConstant))
    }
    anchors.forEach({$0.isActive = true})
    return anchors
}

I can't say what is happening in your code, but you are trying to change constraints very differently than I do. 我无法说出代码中发生了什么,但是您尝试更改约束的方式与我完全不同。 Here's what I tend to do for changing constraints: 这是我倾向于更改约束的方法:

  • Declare all consistant or global constraints - those that will not change "as usual", setting isActive = true . 声明所有一致约束或全局约束-那些不会“像往常一样”更改的约束,设置isActive = true No need to re-write things. 无需重新编写东西。

  • Explicitly set up those constraints that change, either one at a time or in an array, depending on your needs. 明确设置那些可以更改的约束,可以一次变化,也可以数组变化,具体取决于您的需求。

  • Never use removeConstraints ! 切勿使用removeConstraints (While it's maybe more of a personal rule, I've never found a good use case for using it.) Instead set isActive or use NSLayoutConstraint.deactivate and NSLayoutConstraint.activate for your arrays. (尽管这可能更多是个人规则,但我从未找到使用它的好用例。)而是为数组设置isActive或使用NSLayoutConstraint.deactivateNSLayoutConstraint.activate

  • Unless you have a specific need to, do not remove subviews! 除非有特殊需要, 否则不要删除子视图! Messing with the view hierarchy risks ending up with either missing or incomplete constraints. 混乱的视图层次结构最终可能会导致约束丢失或不完整。 Instead, use isHidden = true . 而是使用isHidden = true

I'm thinking it's my last bullet point that's the cause for your result. 我认为这是我最后一个要点,这是造成您结果的原因。

Here's an example of setting up two arrays of constraints, one for portrait and one for landscape orientation. 这是设置两个约束数组的示例,一个约束用于纵向,一个约束用于横向。 Say I have two views that I wish to be above each other in portrait but next to each other in landscape. 假设我有两种观点,我希望它们在肖像中彼此重叠,但在景观中彼此相邻。

In my view controller I have two views: 在我的视图控制器中,我有两个视图:

var p = [NSLayoutConstraint]()
var l = [NSLayoutConstraint]()
var initialOrientation = true
var isInPortrait = false

var greenView = UIView()
var redView = UIView()

override func viewDidLoad() {
    super.viewDidLoad()
    view.translatesAutoresizingMaskIntoConstraints = false
    greenView.translatesAutoresizingMaskIntoConstraints = false
    redView.translatesAutoresizingMaskIntoConstraints = false

    greenView.backgroundColor = UIColor.green
    redView.backgroundColor = UIColor.red

    view.addSubview(greenView)
    view.addSubview(redView)

    setupConstraints()

}

override func viewWillLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if initialOrientation {
        initialOrientation = false
        if view.frame.width > view.frame.height {
            isInPortrait = false
        } else {
            isInPortrait = true
        }
        view.setOrientation(p, l)
    } else {
        if view.orientationHasChanged(&isInPortrait) {
            view.setOrientation(p, l)
        }
    }
}

I create two extensions. 我创建两个扩展。 Here's the view controller extension: 这是视图控制器扩展:

extension ViewController {
    func setupConstraints() {

        // create generic layout guides

        let layoutGuideTop = UILayoutGuide()
        view.addLayoutGuide(layoutGuideTop)

        let layoutGuideBottom = UILayoutGuide()
        view.addLayoutGuide(layoutGuideBottom)

        let margins = view.layoutMarginsGuide
        view.addLayoutGuide(margins)

        if #available(iOS 11, *) {
            let guide = view.safeAreaLayoutGuide
            layoutGuideTop.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
            layoutGuideBottom.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
        } else {
            layoutGuideTop.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
            layoutGuideBottom.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
        }

        p.append(greenView.topAnchor.constraint(equalTo: layoutGuideTop.topAnchor))
        p.append(greenView.leadingAnchor.constraint(equalTo: margins.leadingAnchor))
        p.append(greenView.trailingAnchor.constraint(equalTo: margins.trailingAnchor))
        p.append(greenView.heightAnchor.constraint(equalTo: redView.heightAnchor))

        p.append(redView.topAnchor.constraint(equalTo: greenView.bottomAnchor))
        p.append(redView.leadingAnchor.constraint(equalTo: margins.leadingAnchor))
        p.append(redView.trailingAnchor.constraint(equalTo: margins.trailingAnchor))
        p.append(redView.bottomAnchor.constraint(equalTo: layoutGuideBottom.bottomAnchor))

        l.append(greenView.topAnchor.constraint(equalTo: layoutGuideTop.topAnchor))
        l.append(greenView.leadingAnchor.constraint(equalTo: margins.leadingAnchor))
        l.append(greenView.widthAnchor.constraint(equalTo: redView.widthAnchor))
        l.append(greenView.bottomAnchor.constraint(equalTo: layoutGuideBottom.bottomAnchor))

        l.append(redView.topAnchor.constraint(equalTo: layoutGuideTop.topAnchor))
        l.append(redView.leadingAnchor.constraint(equalTo: greenView.trailingAnchor))
        l.append(redView.bottomAnchor.constraint(equalTo: layoutGuideBottom.bottomAnchor))
        l.append(redView.trailingAnchor.constraint(equalTo:margins.trailingAnchor))
    }
}

The real magic is in a UIView extension, where I activate/deactivate the two arrays: 真正的魔力在于UIView扩展,其中我激活/停用了两个数组:

extension UIView {
    public func orientationHasChanged(_ isInPortrait:inout Bool) -> Bool {
        if self.frame.width > self.frame.height {
            if isInPortrait {
                isInPortrait = false
                return true
            }
        } else {
            if !isInPortrait {
                isInPortrait = true
                return true
            }
        }
        return false
    }
    public func setOrientation(_ p:[NSLayoutConstraint], _ l:[NSLayoutConstraint]) {
        NSLayoutConstraint.deactivate(l)
        NSLayoutConstraint.deactivate(p)
        if self.bounds.width > self.bounds.height {
            NSLayoutConstraint.activate(l)
        } else {
            NSLayoutConstraint.activate(p)
        }
    }
}

The other way I change constraints is by directly declaring something and changing the multiplier, constant, or priority. 我更改约束的另一种方法是直接声明某些内容并更改乘数,常数或优先级。 The main trick is to remember you need to separate setting the isActive flag. 主要技巧是记住您需要单独设置isActive标志。 (Don't ask me why. It's improperly typed in Xcode and I'll bet Apple calls it a "behavior".) (不要问我为什么。它在Xcode中输入不正确,我敢打赌Apple称其为“行为”。)

var myButtonWidth = myButton.widthAnchor.constraint(equalToConstant: 100)
myButtonWidth.isActive = true

And to change is, just code against it: 而要更改的是,只需对其进行编码:

func changeMyButton() {
    myButtonWidth.constant = 200
}

Hopefully some of this helps. 希望其中一些帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM