简体   繁体   English

如何更新 swift 布局锚?

[英]How to update swift Layout Anchors?

Trying to find a solution to update multiple constraints for multiple UI elements on an event.试图找到一种解决方案来更新事件上多个 UI 元素的多个约束。 I have seen some examples of deactivating, making a change, then reactivating constraints, this method seems impractical for the 24 anchors I am working with.我看过一些停用、更改然后重新激活约束的示例,这种方法对于我正在使用的 24 个锚点似乎不切实际。

One of my sets of changes:我的一组更改:

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

Have you tried saving the relevant constraints you created using the layout anchors to properties, and then just changing the constant?您是否尝试将使用布局锚点创建的相关约束保存到属性,然后只更改常量? Eg例如

var ticketTop : NSLayoutConstraint?

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

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

Possibly More Elegant可能更优雅

Depending on your taste for writing extensions, here is a possibly more elegant approach that doesn't use properties, but instead creates extension methods on NSLayoutAnchor and UIView to aid in more succinct usage.根据您编写扩展的喜好,这里有一种可能更优雅的方法,它不使用属性,而是在NSLayoutAnchorUIView上创建扩展方法以帮助更简洁的使用。

First you would write an extension on NSLayoutAnchor like this:首先,你会像这样在NSLayoutAnchor上写一个扩展:

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

This extension allows you to set an identifier on the constraint in the same method call that creates it from an anchor.此扩展允许您在从锚点创建约束的同一方法调用中在约束上设置标识符。 Note that Apple documentation implies that XAxis anchors (left, right, leading, etc.) won't let you create a constraint with YAxis anchors (top, bottom, etc.), but I don't observe this to actually be true.请注意,Apple 文档暗示 XAxis 锚点(左、右、前导等)不会让您使用 YAxis 锚点(顶部、底部等)创建约束,但我不认为这实际上是正确的。 If you did want that type of compiler checking, you would need to write separate extensions for NSLayoutXAxisAnchor , NSLayoutYAxisAnchor , and NSLayoutDimension (for width and height constraints) that enforce the same-axis anchor type requirement.如果您确实想要这种类型的编译器检查,则需要为NSLayoutXAxisAnchorNSLayoutYAxisAnchorNSLayoutDimension (用于宽度和高度约束)编写单独的扩展,以强制执行NSLayoutDimension锚点类型要求。

Next you would write an extension on UIView to get a constraint by identifier:接下来,您将在UIView上编写扩展以通过标识符获取约束:

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

With these extensions in place, your code becomes:有了这些扩展,您的代码将变为:

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

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

Note that using constants or an enum instead of magic string names for the identifiers would be an improvement over the above, but I'm keeping this answer brief and focused.请注意,对标识符使用常量或枚举而不是魔术字符串名称将是对上述的改进,但我将这个答案保持简短和重点。

You can iterate over a view's constraints and check for matching items and anchors.您可以遍历视图的约束并检查匹配的项目和锚点。 Remember that the constraint will be on the view's superview unless it is a dimension constraint.请记住,除非是维度约束,否则约束将在视图的父视图上。 I wrote some helper code that will allow you to find all constraints on one anchor of a view.我编写了一些帮助代码,可以让您找到视图的一个锚点上的所有约束。

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

}

Positioning Single View定位单一视图

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)

I have found another solution.我找到了另一个解决方案。 If you want to change an existing constraint added through Interface Builder you actually need to iterate over the constraints of the superview.如果您想更改通过 Interface Builder 添加的现有约束,您实际上需要迭代超级视图的约束。 This is at least true when you try to change the constant value of alignment constraints.当您尝试更改对齐约束的常量值时,这至少是正确的。

The below example shows the bottom alignment but I assume the same code would work for leading/trailing/top alignment.下面的示例显示了底部对齐,但我认为相同的代码适用于前导/尾随/顶部对齐。

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

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

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