【问题标题】:How to update swift Layout Anchors?如何更新快速布局锚?
【发布时间】:2016-12-30 21:35:36
【问题描述】:

试图找到一种解决方案来更新事件中多个 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

【问题讨论】:

    标签: ios swift autolayout layout-anchor


    【解决方案1】:

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

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

    此扩展允许您在从锚点创建约束的同一方法调用中为约束设置标识符。请注意,Apple 文档暗示 XAxis 锚点(左、右、前导等)不会让您使用 YAxis 锚点(顶部、底部等)创建约束,但我没有观察到这实际上是真的。如果您确实需要这种类型的编译器检查,则需要为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
    }
    

    请注意,对标识符使用常量或枚举而不是魔术字符串名称将是对上述内容的改进,但我会保持这个答案的简短和重点。

    【讨论】:

    • 24+主播一定有更优雅的解决方案?
    • @JustinShulman 我为我的答案添加了另一种方法,您可能会更喜欢
    • 第二种更优雅的方法不起作用!即使我为 NSLayoutXAxisAnchor、NSLayoutYAxisAnchor 和 NSLayoutDimension 创建扩展名,它也可以工作,但要在所有子视图中从顶部查找标识符需要递归,因为同时标识添加到父视图。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      定位单一视图

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

      用法

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

      【讨论】:

        【解决方案4】:

        我找到了另一个解决方案。如果您想更改通过 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()
            }
        

        【讨论】:

          猜你喜欢
          • 2019-06-03
          • 1970-01-01
          • 2015-02-16
          • 2015-01-25
          • 2021-02-02
          • 2020-11-01
          • 1970-01-01
          • 2010-09-09
          • 2013-06-28
          相关资源
          最近更新 更多