如何更新Swift布局锚点?

32

尝试寻找更新多个UI元素的多个约束条件的解决方案。我看过一些禁用、进行更改,然后重新激活约束条件的示例,但对于我正在处理的24个锚点,这种方法似乎不实用。

我的一组更改:

ticketContainer.translatesAutoresizingMaskIntoConstraints = false
ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor).active = true
ticketContainer.leftAnchor.constraintEqualToAnchor(self.rightAnchor, constant: 20).active = true
ticketContainer.widthAnchor.constraintEqualToConstant(200.0).active = true

ticketContainer.leftAnchor.constraintEqualToAnchor(self.leftAnchor, constant: 20).active = true
ticketContainer.widthAnchor.constraintEqualToConstant(100.0).active = true
5个回答

92

你尝试过将使用布局锚点创建的相关约束保存到属性中,然后只更改常量吗?例如:

var ticketTop : NSLayoutConstraint?

func setup() {
    ticketTop = ticketContainer.topAnchor.constraintEqualToAnchor(self.topAnchor, constant:100)
    ticketTop.active = true
}

func update() {
    ticketTop?.constant = 25
}

或许更加优雅

根据您对编写扩展的喜好,这里提供一种可能更加优雅的方法,它不使用属性,而是在NSLayoutAnchorUIView上创建扩展方法以帮助更简洁地使用。

首先,您可以编写一个NSLayoutAnchor的扩展,如下所示:

extension NSLayoutAnchor {
    func constraintEqualToAnchor(anchor: NSLayoutAnchor!, constant:CGFloat, identifier:String) -> NSLayoutConstraint! {
        let constraint = self.constraintEqualToAnchor(anchor, constant:constant)
        constraint.identifier = identifier
        return constraint
    }
}

这个扩展允许你在创建约束时,通过同一方法调用在锚点上设置标识符。请注意,苹果文档暗示X轴锚点(左、右、前导等)不会让你使用Y轴锚点(顶部、底部等)创建约束,但我并没有观察到这实际上是真的。如果您确实希望进行这种类型的编译器检查,则需要编写单独的扩展来限制NSLayoutXAxisAnchorNSLayoutYAxisAnchorNSLayoutDimension (用于宽度和高度约束) 的相同轴锚点类型要求。

接下来,您可以编写一个扩展程序,在UIView上按标识符获取约束:

extension UIView {
    func constraint(withIdentifier:String) -> NSLayoutConstraint? {
        return self.constraints.filter{ $0.identifier == withIdentifier }.first
    }
}

有了这些扩展,你的代码变为:

func setup() {
    ticketContainer.topAnchor.constraintEqualToAnchor(anchor: self.topAnchor, constant:100, identifier:"ticketTop").active = true
}

func update() {
    self.constraint(withIdentifier:"ticketTop")?.constant = 25
}

请注意,使用常量或枚举代替魔术字符串名称作为标识符将优于上述方法,但我希望保持回答简短和重点突出。


3
24个以上的锚点肯定有更优雅的解决方案吧? - Justin
2
@JustinShulman 我在我的答案中添加了一种替代方法,你可能会更喜欢。 - Daniel Hall
2
第二个更优雅的方法不起作用!即使我为NSLayoutXAxisAnchor、NSLayoutYAxisAnchor和NSLayoutDimension创建了扩展,它也可以工作,但要找到标识符需要在所有子视图中递归从顶部开始,因为同时将标识添加到父视图。 - Codenator81

14

您可以遍历视图的约束条件并检查匹配项和锚点。请记住,除非是维度约束,否则约束将在视图的超级视图上。我编写了一些辅助代码,可以让您找到视图一个锚点上的所有约束。

import UIKit

class ViewController: UIViewController {

    let label = UILabel()
    let imageView = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Constraint finder"

        label.translatesAutoresizingMaskIntoConstraints = false
        imageView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        view.addSubview(imageView)

        label.topAnchor.constraint(equalTo: view.topAnchor, constant: 30).isActive = true
        label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
        label.widthAnchor.constraint(greaterThanOrEqualToConstant: 50).isActive = true

        imageView.topAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
        imageView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 60).isActive = true
        imageView.widthAnchor.constraint(equalTo: label.widthAnchor).isActive = true
        imageView.heightAnchor.constraint(equalToConstant: 70).isActive = true

        print("Label's top achor constraints: \(label.constraints(on: label.topAnchor))")
        print("Label's width achor constraints: \(label.constraints(on: label.widthAnchor))")
        print("ImageView's width achor constraints: \(imageView.constraints(on: imageView.widthAnchor))")
    }

}

public extension UIView {

    public func constraints(on anchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
        guard let superview = superview else { return [] }
        return superview.constraints.filtered(view: self, anchor: anchor)
    }

    public func constraints(on anchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
        guard let superview = superview else { return [] }
        return superview.constraints.filtered(view: self, anchor: anchor)
    }

    public func constraints(on anchor: NSLayoutDimension) -> [NSLayoutConstraint] {
        guard let superview = superview else { return [] }
        return constraints.filtered(view: self, anchor: anchor) + superview.constraints.filtered(view: self, anchor: anchor)
    }

}

extension NSLayoutConstraint {

    func matches(view: UIView, anchor: NSLayoutYAxisAnchor) -> Bool {
        if let firstView = firstItem as? UIView, firstView == view && firstAnchor == anchor {
            return true
        }
        if let secondView = secondItem as? UIView, secondView == view && secondAnchor == anchor {
            return true
        }
        return false
    }

    func matches(view: UIView, anchor: NSLayoutXAxisAnchor) -> Bool {
        if let firstView = firstItem as? UIView, firstView == view && firstAnchor == anchor {
            return true
        }
        if let secondView = secondItem as? UIView, secondView == view && secondAnchor == anchor {
            return true
        }
        return false
    }

    func matches(view: UIView, anchor: NSLayoutDimension) -> Bool {
        if let firstView = firstItem as? UIView, firstView == view && firstAnchor == anchor {
            return true
        }
        if let secondView = secondItem as? UIView, secondView == view && secondAnchor == anchor {
            return true
        }
        return false
    }
}

extension Array where Element == NSLayoutConstraint {

    func filtered(view: UIView, anchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
        return filter { constraint in
            constraint.matches(view: view, anchor: anchor)
        }
    }
    func filtered(view: UIView, anchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
        return filter { constraint in
            constraint.matches(view: view, anchor: anchor)
        }
    }
    func filtered(view: UIView, anchor: NSLayoutDimension) -> [NSLayoutConstraint] {
        return filter { constraint in
            constraint.matches(view: view, anchor: anchor)
        }
    }

}

2

定位单个视图

最初的回答
extension UIView {
    func add(view: UIView, left: CGFloat, right: CGFloat, top: CGFloat, bottom: CGFloat) {
        
        view.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(view)

        view.leftAnchor.constraint(equalTo: self.leftAnchor, constant: left).isActive = true
        view.rightAnchor.constraint(equalTo: self.rightAnchor, constant: right).isActive = true
        
        view.topAnchor.constraint(equalTo: self.topAnchor, constant: top).isActive = true
        view.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: bottom).isActive = true
        
    }
}

Usage

headerView.add(view: headerLabel, left: 20, right: 0, top: 0, bottom: 0)

0

我找到了另一种解决方案。如果您想更改通过Interface Builder添加的现有约束,则实际上需要迭代父视图的约束。当您尝试更改对齐约束的常量值时,至少是这样。

下面的示例显示底部对齐,但我认为相同的代码也适用于前导/后续/顶部对齐。

    private func bottomConstraint(view: UIView) -> NSLayoutConstraint {
        guard let superview = view.superview else {
            return NSLayoutConstraint()
        }

        for constraint in superview.constraints {
            for bottom in [NSLayoutConstraint.Attribute.bottom, NSLayoutConstraint.Attribute.bottomMargin] {
                if constraint.firstAttribute == bottom && constraint.isActive && view == constraint.secondItem as? UIView {
                    return constraint
                }

                if constraint.secondAttribute == bottom && constraint.isActive && view == constraint.firstItem as? UIView {
                    return constraint
                }
            }
        }

        return NSLayoutConstraint()
    }

0

有用的扩展

extension UIView {

    //Note: Use `leadingAnchor and trailingAnchor` instead of `leftAnchor and rightAnchor`, it is going to help in RTL
    func addConstraint(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, paddingTop: CGFloat? = nil, paddingLeft: CGFloat? = nil, paddingBottom: CGFloat? = nil, paddingRight: CGFloat? = nil, width: CGFloat? = nil, height: CGFloat? = nil) {
        translatesAutoresizingMaskIntoConstraints = false
        //top constraint
        if let top = top {
            self.topAnchor.constraint(equalTo: top, constant: paddingTop ?? 0).isActive = true
        }
    
        // leading/left constraint
        if let leading = leading {
            self.leadingAnchor.constraint(equalTo: leading, constant: paddingLeft ?? 0).isActive = true
        }

        //  bottom constraint
        if let bottom = bottom {
            self.bottomAnchor.constraint(equalTo: bottom, constant: -(paddingBottom ?? 0) ).isActive = true
        }

        // trailing/right constraint
        if let trailing = trailing {
            self.trailingAnchor.constraint(equalTo: trailing, constant: -(paddingRight ?? 0)).isActive = true
        }

        //width constraint
        if let width = width {
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }

        //height constraint
        if let height = height {
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }
    }
}

如何使用

 let myView = UIView()
 self.view.addSubview(myView)
 myView.backgroundColor = .yellow
 myView.addConstraint(top: self.view.safeAreaLayoutGuide.topAnchor, leading: self.view.safeAreaLayoutGuide.leadingAnchor, bottom: self.view.safeAreaLayoutGuide.bottomAnchor, trailing: self.view.safeAreaLayoutGuide.trailingAnchor, paddingTop: 100)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接