简体   繁体   English

如何“找到”自己的约束?

[英]How to "find" your own constraint?

Say I have a UIView,假设我有一个 UIView,

 class CleverView: UIView

In the custom class, I want to do this:在自定义 class 中,我想这样做:

func changeWidth() {

  let c = ... find my own layout constraint, for "width"
  c.constant = 70 * Gameinfo.ImportanceOfEnemyFactor
}

Similarly I wanna be able to "find" like that, the constraint (or I guess, all constraints, there could be more than one) attached to one of the four edges.同样,我希望能够像那样“找到”约束(或者我猜,所有约束,可能不止一个)附加到四个边之一。

So, to look through all the constraints attached to me, and find any width/height ones, or indeed any relevant to a given (say, "left") edge.因此,查看所有附加到我的约束,并找到任何宽度/高度的约束,或者实际上与给定(例如,“左”)边缘相关的任何约束。

Any ideas?有任何想法吗?

It's perhaps worth noting this question也许值得注意这个问题


Please, note that (obviously) I am asking how to do this dynamically/programmatically.请注意(显然)我在询问如何动态/以编程方式执行此操作。

(Yes, you can say "link to the constraint" or "use an ID" - the whole point of the QA is how to find them on the fly and work dynamically.) (是的,您可以说“链接到约束”或“使用 ID”——QA 的重点是如何动态找到它们并动态工作。)

If you are new to constraints, note that .constraints just gives you the ends stored "there".如果您不熟悉约束,请注意.constraints只为您提供存储在“那里”的末端。

There are really two cases:其实有两种情况:

  1. Constraints regarding a view's size or relations to descendant views are saved in itself关于视图大小或与后代视图关系的约束被保存在自身中
  2. Constraints between two views are saved in the views' lowest common ancestor两个视图之间的约束保存在视图的最低共同祖先中

To repeat.重复。 For constraints which are between two views.对于两个视图之间的约束。 iOS does, in fact, always store them in the lowest common ancestor.实际上,iOS 确实总是将它们存储在最低的公共祖先中。 Thus, a constraint of a view can always be found by searching all ancestors of the view.因此,总是可以通过搜索视图的所有祖先来找到视图的约束。

Thus, we need to check the view itself and all its superviews for constraints.因此,我们需要检查视图本身及其所有超视图的约束。 One approach could be:一种方法可能是:

extension UIView {

    // retrieves all constraints that mention the view
    func getAllConstraints() -> [NSLayoutConstraint] {

        // array will contain self and all superviews
        var views = [self]

        // get all superviews
        var view = self
        while let superview = view.superview {
            views.append(superview)
            view = superview
        }

        // transform views to constraints and filter only those
        // constraints that include the view itself
        return views.flatMap({ $0.constraints }).filter { constraint in
            return constraint.firstItem as? UIView == self ||
                constraint.secondItem as? UIView == self
        }
    }
}

You can apply all kinds of filters after getting all constraints about a view, and I guess that's the most difficult part.在获得有关视图的所有约束后,您可以应用各种过滤器,我想这是最困难的部分。 Some examples:一些例子:

extension UIView {

    // Example 1: Get all width constraints involving this view
    // We could have multiple constraints involving width, e.g.:
    // - two different width constraints with the exact same value
    // - this view's width equal to another view's width
    // - another view's height equal to this view's width (this view mentioned 2nd)
    func getWidthConstraints() -> [NSLayoutConstraint] {
        return getAllConstraints().filter( {
            ($0.firstAttribute == .width && $0.firstItem as? UIView == self) ||
            ($0.secondAttribute == .width && $0.secondItem as? UIView == self)
        } )
    }

    // Example 2: Change width constraint(s) of this view to a specific value
    // Make sure that we are looking at an equality constraint (not inequality)
    // and that the constraint is not against another view
    func changeWidth(to value: CGFloat) {

        getAllConstraints().filter( {
            $0.firstAttribute == .width &&
                $0.relation == .equal &&
                $0.secondAttribute == .notAnAttribute
        } ).forEach( {$0.constant = value })
    }

    // Example 3: Change leading constraints only where this view is
    // mentioned first. We could also filter leadingMargin, left, or leftMargin
    func changeLeading(to value: CGFloat) {
        getAllConstraints().filter( {
            $0.firstAttribute == .leading &&
                $0.firstItem as? UIView == self
        }).forEach({$0.constant = value})
    }
}

// edit: Enhanced examples and clarified their explanations in comments // 编辑:增强示例并在注释中阐明了它们的解释

I guess you can work with constraints property of UIView .我猜你可以使用UIView 约束属性。 constraints basically returns an array of constraint directly assigned to UIView. constraints基本上返回一个直接分配给 UIView 的约束数组。 It will not be able to get you the constraints held by superview such as leading, trailing, top or bottom but width and height constraints are held by View itself.它无法让您获得 superview 所持有的约束,例如前导、尾随、顶部或底部,但宽度和高度约束由 View 本身持有。 For superview's constraints, you can loop through superview's constraints.对于 superview 的约束,您可以遍历 superview 的约束。 Lets say the clever view has these constraints:让我们说聪明的观点有这些限制:

在此处输入图片说明

class CleverView: UIView {

    func printSuperViewConstriantsCount() {
        var c = 0
        self.superview?.constraints.forEach({ (constraint) in
            guard constraint.secondItem is CleverView || constraint.firstItem is CleverView else {
                return
            }
            c += 1
            print(constraint.firstAttribute.toString())
        })
        print("superview constraints:\(c)")
    }

    func printSelfConstriantsCount() {
        self.constraints.forEach { (constraint) in
            return print(constraint.firstAttribute.toString())
        }
        print("self constraints:\(self.constraints.count)")
    }
}

Output :输出

top最佳
leading领导
trailing尾随
superview constraints:3超视图约束:3
height高度
self constraints:1自我约束:1

Basically, you can look at NSLayoutConstraint class to get the info out about a particular constraint.基本上,您可以查看NSLayoutConstraint类以获取有关特定约束的信息。

To print the name of constraints, we can use this extension要打印约束的名称,我们可以使用此扩展名

extension NSLayoutAttribute {
    func toString() -> String {
        switch self {
        case .left:
            return "left"
        case .right:
            return "right"
        case .top:
            return "top"
        case .bottom:
            return "bottom"
        case .leading:
            return "leading"
        case .trailing:
            return "trailing"
        case .width:
            return "width"
        case .height:
            return "height"
        case .centerX:
            return "centerX"
        case .centerY:
            return "centerY"
        case .lastBaseline:
            return "lastBaseline"
        case .firstBaseline:
            return "firstBaseline"
        case .leftMargin:
            return "leftMargin"
        case .rightMargin:
            return "rightMargin"
        case .topMargin:
            return "topMargin"
        case .bottomMargin:
            return "bottomMargin"
        case .leadingMargin:
            return "leadingMargin"
        case .trailingMargin:
            return "trailingMargin"
        case .centerXWithinMargins:
            return "centerXWithinMargins"
        case .centerYWithinMargins:
            return "centerYWithinMargins"
        case .notAnAttribute:
            return "notAnAttribute"
        }
    }
}

stakri 's answer is OK, but we can do better by using sequence(first:next:) : stakri的回答没问题,但我们可以通过使用sequence(first:next:)来做得更好:

extension UIView {
    var allConstraints: [NSLayoutConstraint] {
        sequence(first: self, next: \.superview)
            .flatMap(\.constraints)
            .lazy
            .filter { constraint in
                constraint.firstItem as? UIView == self || constraint.secondItem as? UIView == self
            }
    }
}

Then, if we check both implementations by swift-benchmark by Google we can see that Sequence implementation is much faster (almost +50k iterations for the ±same time).然后,如果我们通过 Google 的swift-benchmark检查这两个实现,我们可以看到Sequence实现要快得多(几乎同时进行 +50k 次迭代)。

running Find All Constraints: Stakri... done! (1778.86 ms)
running Find All Constraints: Sequence... done! (1875.20 ms)

name                          time        std        iterations
---------------------------------------------------------------
Find All Constraints.Stakri   3756.000 ns ±  96.67 %     291183
Find All Constraints.Sequence 3727.000 ns ± 117.42 %     342261

Might save someone some typing.......可能会为某人节省一些打字时间......

Based on stakri's bounty-winning answer, here is exactly how to get基于 stakri 的赏金答案,这正是如何获得

all constraints of the type "fractional width of another view" “另一个视图的部分宽度”类型的所有约束

all constraints of the type "fixed point width" “固定点宽度”类型的所有约束

all constraints of the type "your x position" “您的 x 位置”类型的所有约束

So ..所以..

 fileprivate extension UIView {
    func widthAsPointsConstraints()->[NSLayoutConstraint] {}
    func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {}
    func xPositionConstraints()->[NSLayoutConstraint]
}

Full code below.完整代码如下。 Of course, you can do "height" the same way.当然,你可以用同样的方法来做“高度”。

So, use them like this...所以,像这样使用它们......

let cc = someView.widthAsFractionOfAnotherViewConstraints()
for c in cc {
   c.changeToNewConstraintWith(multiplier: 0.25)
}

or

let cc = someView.widthAsPointsConstraints()
for c in cc {
    c.constant = 150.0
}

Also, at the bottom I pasted in a simple demo code, example output...另外,在底部我粘贴了一个简单的演示代码,示例输出...

在此处输入图片说明

Here's the code.这是代码。 V2 ... V2 ...

fileprivate extension UIView { // experimental
    
    func allConstraints()->[NSLayoutConstraint] {
        
        var views = [self]
        var view = self
        while let superview = view.superview {
            
            views.append(superview)
            view = superview
        }
        
        return views.flatMap({ $0.constraints }).filter { constraint in
            return constraint.firstItem as? UIView == self ||
                constraint.secondItem as? UIView == self
        }
    }
    
     func widthAsPointsConstraints()->[NSLayoutConstraint] {

        return self.allConstraints()
         .filter({
            ( $0.firstItem as? UIView == self && $0.secondItem == nil )
         })
         .filter({
            $0.firstAttribute == .width && $0.secondAttribute == .notAnAttribute
         })
    }
    
    func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {
        
        func _bothviews(_ c: NSLayoutConstraint)->Bool {
            if c.firstItem == nil { return false }
            if c.secondItem == nil { return false }
            if !c.firstItem!.isKind(of: UIView.self) { return false }
            if !c.secondItem!.isKind(of: UIView.self) { return false }
            return true
        }
        
        func _ab(_ c: NSLayoutConstraint)->Bool {
            return _bothviews(c)
                && c.firstItem as? UIView == self
                && c.secondItem as? UIView != self
                && c.firstAttribute == .width
        }
        
        func _ba(_ c: NSLayoutConstraint)->Bool {
            return _bothviews(c)
                && c.firstItem as? UIView != self
                && c.secondItem as? UIView == self
                && c.secondAttribute == .width
        }
        
        // note that .relation could be anything: and we don't mind that
        
        return self.allConstraints()
            .filter({ _ab($0) || _ba($0) })
    }

     func xPositionConstraints()->[NSLayoutConstraint] {

         return self.allConstraints()
            .filter({
         return $0.firstAttribute == .centerX || $0.secondAttribute == .centerX
         })
    }
}

extension NSLayoutConstraint {
    
    // typical routine to "change" multiplier fraction...
    
    @discardableResult
    func changeToNewConstraintWith(multiplier:CGFloat) -> NSLayoutConstraint {

        //NSLayoutConstraint.deactivate([self])
        self.isActive = false
        
        let nc = NSLayoutConstraint(
            item: firstItem as Any,
            attribute: firstAttribute,
            relatedBy: relation,
            toItem: secondItem,
            attribute: secondAttribute,
            multiplier: multiplier,
            constant: constant)

        nc.priority = priority
        nc.shouldBeArchived = self.shouldBeArchived
        nc.identifier = self.identifier

        //NSLayoutConstraint.activate([nc])
        nc.isActive = true
        return nc
    }
}

Just an example demo...只是一个示例演示...

override func viewDidAppear(_ animated: Bool) {
    
    super.viewDidAppear(animated)
    
    _teste()
    
    delay(5) {
        print("changing any 'fraction fo another view' style widths ...\n\n")
        let cc = self.animeHolder.widthAsFractionOfAnotherViewConstraints()
        for c in cc {
            c.changeToNewConstraintWith(multiplier: 0.25)
        }
        self._teste()
    }
    
    delay(10) {
        print("changing any 'points' style widths ...\n\n")
        let cc = self.animeHolder.widthAsPointsConstraints()
        for c in cc {
            c.constant = 150.0
        }
        self._teste()
    }
}

func _teste() {
    
    print("\n---- allConstraints")
    for c in animeHolder.allConstraints() {
        print("\n \(c)")
    }
    print("\n---- widthAsPointsConstraints")
    for c in animeHolder.widthAsPointsConstraints() {
        print("\n \(c)\n \(c.multiplier) \(c.constant)")
    }
    print("\n---- widthAsFractionOfAnotherViewConstraints")
    for c in animeHolder.widthAsFractionOfAnotherViewConstraints() {
        print("\n \(c)\n \(c.multiplier) \(c.constant)")
    }
    print("\n----\n")
}

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

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