【问题标题】:Change anchors/Constraints when a button is pressed按下按钮时更改锚点/约束
【发布时间】:2017-11-13 02:29:34
【问题描述】:

我在 UIViewController 中添加了一个子视图。这个子视图(称为 PlayerView)应该有两种状态。一种是折叠状态,高度只有56。展开状态的高度是385。当子视图从折叠变为展开时,我希望PlayerView中的子视图改变形状和位置。

当将 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

}

当按下折叠视图时,它应该展开。以下是我的做法(在我的 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

    }
}

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        
}

执行完所有这些代码后会发生什么: 1.playerView扩展为385罚款 2.从playerView中移除所有其他子视图 3.播放暂停按钮几乎占据了整个PlayerView子view,不符合其22的宽高约束

我需要做什么才能让 playPauseButton 符合它的所有约束?

编辑:

.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
}

【问题讨论】:

  • 我很难通过您的代码 - 可能只是编码风格的差异。但是 - 我看到 no 定义或命名约束 也没有 现有约束数组的任何激活/停用。这是在定义约束之后更改约束的两种常用方法。我错过了什么吗?换句话说,将事物分解为您希望更改的 single 约束 - 例如高度或宽度,您打算如何(以及何时)更改它?
  • 查看底部函数‘anchor’。这就是我设置约束的方式
  • 我做到了。仍然过得很艰难。 具体你想做什么? 具体是什么问题?再说一次,发生了什么变化? (更好的是,你能给我一些我可以实际复制的东西吗?编辑:我已经设置了改变的约束。滑动菜单、方向改变等。你发布的代码中没有任何意义!我得到您的 anchor 函数将 everything 设置为 isActive = true。这与您的问题标题不符 - “按下按钮时更改约束”。
  • 问题是,当我更改约束(通过运行 changePlayerView)时,playPauseButton 按钮不是 22x22。它占据了整个 playerView,即 375x385。你能通过我给你的代码告诉你原因吗?如果需要,我可以提供更多代码

标签: ios swift subview


【解决方案1】:

我无法说出您的代码中发生了什么,但您尝试更改约束的方式与我不同。以下是我倾向于更改约束的方法:

  • 声明所有一致全局约束——那些不会“像往常一样”改变的约束,设置isActive = true。无需重写。

  • 根据您的需要,显式设置更改的约束,一次一个或一个数组。

  • 永远不要使用removeConstraints! (虽然这可能更像是个人规则,但我从来没有找到使用它的好用例。)而是为您的数组设置 isActive 或使用 NSLayoutConstraint.deactivateNSLayoutConstraint.activate

  • 除非您有特殊需要,否则不要删除子视图!弄乱视图​​层次结构可能会导致丢失或不完整的约束。请改用isHidden = true

我认为这是我的最后一个要点导致你的结果。

这是一个设置两个约束数组的示例,一个用于纵向,一个用于横向。假设我有两个视图,我希望在纵向上彼此位于上方,但在横向上彼此相邻。

在我的视图控制器中,我有两个视图:

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)
        }
    }
}

我创建了两个扩展。这是视图控制器扩展:

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))
    }
}

真正的魔力在于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)
        }
    }
}

我更改约束的另一种方法是直接声明某些内容并更改乘数、常数或优先级。主要技巧是记住您需要单独设置isActive 标志。 (不要问我为什么。它在 Xcode 中输入不正确,我敢打赌 Apple 称其为“行为”。)

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

要改变的是,只需针对它编写代码:

func changeMyButton() {
    myButtonWidth.constant = 200
}

希望其中的一些帮助。

【讨论】:

  • 你在哪里声明myButtonWidth var?它不会让我在函数之外声明它,所以如果它只在它声明的函数中可用,我怎么能改变它(例如viewDidLoad()
  • 在比函数内部“更宽”的范围内声明它。我通常在类级别调用它,因为约束往往与存在于该级别的视图有关。在我的示例中,myButtonUIViewController 类中的任何函数都可用的视图(如果需要),对吧?所以以完全相同的方式处理约束。实际上,我已经在框架目标中使用了约束,就像将任何参数传入和传出函数一样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 2015-10-07
  • 2013-04-02
  • 1970-01-01
相关资源
最近更新 更多