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

mjqavswn  于 2022-11-19  发布在  iOS
关注(0)|答案(4)|浏览(161)

假设我有一个UIView,

class CleverView: UIView

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

func changeWidth() {

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

类似地,我希望能够“找到”这样的约束(或者我猜,所有约束,可能不止一个),附加到四个边中的一个。
因此,要查看附加到我的所有约束,并找到任何宽度/高度约束,或者实际上任何与给定(比如“左”)边缘相关的约束。
有什么想法吗?
也许值得注意的是this question
请注意,(很明显)我问的是如何以动态/编程方式完成这一点。
(Yes,您可以说“链接到约束”或“使用ID”-QA的全部要点是如何动态地找到它们并动态地工作。)
如果您是约束的新手,请注意.constraints只给您存储在“那里”的端点。

webghufk

webghufk1#

实际上有两种情况:
1.有关视图大小或与后代视图关系的约束保存在其自身中
1.两个视图之间的约束保存在视图的最低公共祖先中

**重复一遍。对于两个视图之间的约束,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})
    }
}

// edit:增强了示例并在注解中澄清了它们的解释

wj8zmpe1

wj8zmpe12#

我想你可以使用UIViewconstraints属性。constraints基本上返回一个直接分配给UIView的约束数组。它不能让你得到由超级视图拥有的约束,如前导、尾随、顶部或底部,但宽度和高度约束由View本身拥有。对于超级视图的约束,你可以遍历超级视图的约束。假设智能视图有这些约束:

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

输出

顶部
前导
尾随的
超级视图约束:3
高度
自约束:1
基本上,您可以查看NSLayoutConstraint类来获取有关特定约束的信息。
要打印约束的名称,我们可以使用以下扩展名

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

1aaf6o9v3#

stakri 's answer是可以的,但我们可以使用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
            }
    }
}

然后,如果我们检查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
doinxwow

doinxwow4#

可能会保存一些打字的人......。
根据stakri的有奖答案,以下是如何获得

类型为“另一视图的分数宽度”的所有约束

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

“您的x位置”类型的所有约束

所以...

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

下面是完整的代码。当然,你也可以用同样的方法来做“height”。
所以,像这样使用它们...

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
}

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

这是密码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
    }
}

只是一个示例演示...

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

相关问题