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

时间:2017-11-01 11:24:36

标签: ios swift nslayoutconstraint

说我有一个UIView,

 class CleverView: UIView

在自定义类中,我想这样做:

func changeWidth() {

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

同样地,我希望能够像这样“找到”,约束(或者我猜,所有约束,可能有多个约束)附加到四个边缘之一。

因此,要查看附加到我身上的所有约束,找到任何宽度/高度,或者确实任何与给定(例如“左”)边缘相关的约束。

有什么想法吗?

或许值得注意this question

请注意(显然)我在询问如何动态/编程地执行此操作。

(是的,你可以说“链接到约束”或“使用ID” - 质量保证的重点是如何动态找到它们并动态工作。)

如果您不熟悉约束,请注意.constraints只是为您提供“存在”的结尾。

3 个答案:

答案 0 :(得分:17)

实际上有两种情况:

  1. 关于视图大小或与后代视图的关系的约束本身已保存
  2. 两个视图之间的约束保存在视图的最低共同祖先
  3. 要重复。对于两个视图之间的约束。事实上,iOS确实将它们存储在最低的共同祖先中。因此,通过搜索视图的所有祖先,总是可以找到视图的约束。

    因此,我们需要检查视图本身及其所有超视图的约束。一种方法可能是:

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

    您可以在获得有关视图的所有约束后应用所有类型的过滤器,我猜这是最难的部分。一些例子:

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

    //编辑:增强的示例并在评论中阐明了他们的解释

答案 1 :(得分:9)

我猜您可以使用constraints的{​​{3}}属性。 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)") } } 基本上返回一个直接分配给UIView的约束数组。它不能让你获得超级视图所包含的约束,例如前导,尾随,顶部或底部,但宽度和高度约束由View本身保持。对于superview的约束,您可以遍历superview的约束。让我们说聪明的观点有这些限制:

constraints

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

<强>输出


领先
尾随
超视限制:3
高度
自我约束:1

基本上,您可以查看enter image description here类以获取有关特定约束的信息。

要打印约束的名称,我们可以使用此扩展名

RouterModule.forRoot(routes, {useHash: false});

答案 2 :(得分:0)

可以拯救某人打字.......

根据stakri获得赏金的答案,以下是如何获得

类型“另一个视图的分数宽度”

的所有约束

“固定点宽度”类型的所有约束

所以......

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

以下全部代码。当然,你可以用同样的方式做“身高”。

所以,像这样使用它们......

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

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

另外,在底部我粘贴了一个简单的演示代码,示例输出......

enter image description here

这是kode。 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) })
    }
}

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

只是一个示例演示...

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