删除所有影响UIView的约束


87

我有一个通过多个约束放置在屏幕上的UIView。某些约束由超级视图拥有,而其他约束则由其他祖先拥有(例如,UIViewController的view属性)。

我想删除所有这些旧约束,并使用新约束将其放置在新位置。

在不为每个约束创建IBOutlet且必须记住哪个视图拥有所述约束的情况下,如何做到这一点?

详细地说,幼稚的方法是为每个约束创建一堆IBOutlet,然后涉及调用代码,例如:

[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];

此代码的问题在于,即使在最简单的情况下,我也需要创建2个IBOutlet。对于复杂的布局,这可以轻松达到4或8个所需的IBOutlets。此外,我需要确保在适当的视图上正在调用删除约束的调用。例如,假设myViewsLeftConstraint归拥有viewA。如果我不小心打了电话[self.view removeConstraint:self.myViewsLeftConstraint],什么也不会发生。

注意:方法constraintsAffectingLayoutForAxis看起来很有希望,但仅用于调试目的。


更新:很多我收到处理问题的答案self.constraintsself.superview.constraints或一些人的变种。这些解决方案将不起作用,因为这些方法仅返回视图所拥有的约束,而不会影响那些约束视图。

为了阐明这些解决方案的问题,请考虑以下视图层次结构:

  • 祖父
    • 父亲
        • 儿子
        • 女儿
      • 哥哥
    • 叔叔

现在想象我们创建以下约束,并将它们始终附加到最接近的共同祖先:

  • C0:我:与儿子(我拥有)同上
  • C1:我:宽度= 100(属于我)
  • C2:我:与兄弟(父亲拥有)相同的身高
  • C3:我:和叔叔一样(祖父拥有)
  • C4:我:与祖父(祖父拥有)同左
  • C5:兄弟:与父亲相同(父亲拥有)
  • C6:叔叔:与祖父(祖父拥有)相同
  • C7:儿子:和女儿(我所有)同左

现在想象我们要删除所有影响的约束Me。任何适当的解决方案都应该删除,[C0,C1,C2,C3,C4]并且别无其他。

如果我使用self.constraints(自我就是我),我将得到[C0,C1,C7],因为这些是我所拥有的唯一约束。显然,仅仅删除它是不够的,因为它丢失了[C2,C3,C4]。此外,它是C7不必要地删除。

如果我使用self.superview.constraints(自我就是我),我会得到[C2,C5],因为那是父亲的约束。显然,我们无法删除所有这些内容,因为它们C5与完全无关Me

如果我使用grandfather.constraints,我会得到[C3,C4,C6]。同样,我们不能删除所有这些内容,因为它们C6应保持不变。

蛮力方法是循环在每个视图的祖先(包括其本身)的,并且如果看到firstItemsecondItem是视图本身; 如果是这样,请删除该约束。这将导致正确的解决方案,return [C0,C1,C2,C3,C4],并且仅是那些约束。

但是,我希望有一个比必须遍历整个祖先列表更优雅的解决方案。


您如何将标识符添加到要删除的所有约束中?这样,您无需为它们保留出口。
nsuinteger '16

Answers:


69

这种方法对我有用:

@interface UIView (RemoveConstraints)

- (void)removeAllConstraints;

@end


@implementation UIView (RemoveConstraints)

- (void)removeAllConstraints
{
    UIView *superview = self.superview;
    while (superview != nil) {
        for (NSLayoutConstraint *c in superview.constraints) {
            if (c.firstItem == self || c.secondItem == self) {
                [superview removeConstraint:c];
            }
        }
        superview = superview.superview;
    }

    [self removeConstraints:self.constraints];
    self.translatesAutoresizingMaskIntoConstraints = YES;
}

@end

完成后,执行视图将保持原样,因为它会创建自动调整大小的约束。当我不这样做时,视图通常会消失。此外,它不仅会从超级视图中删除约束,而且会一直向上遍历,因为在祖先视图中可能会有一些影响它的约束。


Swift 4版本

extension UIView {
    
    public func removeAllConstraints() {
        var _superview = self.superview
        
        while let superview = _superview {
            for constraint in superview.constraints {
                
                if let first = constraint.firstItem as? UIView, first == self {
                    superview.removeConstraint(constraint)
                }
                
                if let second = constraint.secondItem as? UIView, second == self {
                    superview.removeConstraint(constraint)
                }
            }
            
            _superview = superview.superview
        }
        
        self.removeConstraints(self.constraints)
        self.translatesAutoresizingMaskIntoConstraints = true
    }
}

3
这应该是官方的答案。
杰森·克罗克

你为什么要有self.translatesAutoresizingMaskIntoConstraints = YES; ?从字面上讲,您根本就不想在要设置约束的视图中使用它吗?
lensovet

4
我正在删除约束,因此在删除其他约束以将其固定在位后,我想使用自动调整大小约束,如果代码中没有该行,视图将消失
marchinram

我需要将所有约束记录在视图中以进行调试,并且能够对此答案进行一些修改。+1
chiliNUT

这将错误地删除C7。但是,如果删除[self removeConstraints:self.constraints];并更改UIView *superview = self.superview;为,应该很容易修复UIView *superview = self;
有意义的

49

到目前为止,我发现的唯一解决方案是从其超级视图中删除该视图:

[view removeFromSuperview]

看起来它已删除所有影响其布局的约束,并准备添加到超级视图中并附加了新的约束。但是,它也会错误地从层次结构中删除所有子视图,并且会被错误地清除[C7]


您可以使用约束的firstItem和secondItem属性来检查它们是否适用于您的视图,并循环遍历视图层次结构以查找所有约束。我认为删除和重新添加是更好的解决方案。我很想看看您的用例,因为这样做似乎很不好。您的约束很容易变得完全无效,因为其他视图可能依靠这些约束来正确布局。
bjtitus

@bjtitus:好点。但是,在这种特定情况下,我将重新定位不依赖于此视图的视图。它使用其他视图来知道要放置的位置,没有视图使用它来知道要放置的位置。
有意义的2014年

这不是唯一的解决方案,但非常简单。在我的用例中,我刚刚从超级视图中删除了该视图,然后在以后重新添加了它。
玛丽安·塞尔尼

48

您可以通过执行以下操作删除视图中的所有约束:

self.removeConstraints(self.constraints)

编辑:要删除所有子视图的约束,请在Swift中使用以下扩展名:

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}

22
该解决方案的问题在于,它仅消除了该视图拥有的约束(例如,其宽度和高度)。它不会删除诸如领先约束和最高约束之类的东西。
有意义的2014年

2
我尚未验证您在上面的评论。但是,我相信这将与上面的相同或更好,[NSLayoutConstraint deactivateConstraints:self.constraints];
emdog4 2014年

2
这将遭受与原始解决方案相同的问题。self.constraints仅返回self拥有的约束,而不返回所有影响的约束self
有意义的2014年

1
我明白你现在在说什么。在我的回答中,我正在从UITableViewCell的contentView中删除约束。然后,方法updateConstraints将为所有子视图添加约束并重置布局。在上面的第一条评论中,我应该输入self.view.constraints。self.view和self.contentView都位于视图层次结构的顶部。在self.view或self.contentView上设置translatesAutoresizingMasksToConstraints = YES是一个坏主意。;-)。我不喜欢对addSubview的调用:在我的updateConstraints方法中,从层次结构中删除视图似乎是不必要的imo。
emdog4 2014年

我刚刚更新了问题,以说明为什么这样的解决方案无法满足我的需求。假设您位于UITableViewCell中,那么contentView相当于祖父。因此,您的解决方案将错误地删除[C6],而错误地不删除[C0,C1,C2]
有意义的2015年

20

根据Apple开发人员文档,有两种方法可以实现该目标

1。 NSLayoutConstraint.deactivateConstraints

这是一种便捷的方法,它提供了一种轻松的方法来通过一个调用停用一组约束。此方法的效果与将每个约束的isActive属性设置为false相同。通常,使用此方法比分别停用每个约束更为有效。

// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2。 UIView.removeConstraints(不推荐用于> = iOS 8.0)

在针对iOS 8.0或更高版本进行开发时,请使用NSLayoutConstraint类的deactivateConstraints:方法,而不是直接调用removeConstraints:方法。deactivateConstraints:方法会自动从正确的视图中删除约束。

// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`

// Usage
yourView.removeConstraints(yourView.constraints)

提示

使用Storyboards或XIBs可能会像配置方案中所述那样配置约束时很痛苦,您必须为要删除的每个约束创建IBOutlet。即便如此,大多数时间Interface Builder造成的麻烦多于解决的麻烦。

因此,当具有非常动态的内容和不同的视图状态时,我建议:

  1. 以编程方式创建视图
  2. 布置它们并使用NSLayoutAnchor
  3. 将可能稍后删除的每个约束附加到数组
  4. 每次应用新状态之前都要清除它们

简单代码

private var customConstraints = [NSLayoutConstraint]()

private func activate(constraints: [NSLayoutConstraint]) {
    customConstraints.append(contentsOf: constraints)
    customConstraints.forEach { $0.isActive = true }
}

private func clearConstraints() {
    customConstraints.forEach { $0.isActive = false }
    customConstraints.removeAll()
}

private func updateViewState() {
    clearConstraints()

    let constraints = [
        view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
        view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
        view.topAnchor.constraint(equalTo: parentView.topAnchor),
        view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
    ]

    activate(constraints: constraints)

    view.layoutIfNeeded()
}

参考文献

  1. NSLayoutConstraint
  2. UIView

2
小心。指定内部内容大小的视图将由iOS自动添加NSContentSizeLayoutConstraint。通过访问常规的yourView.constraints属性将其删除会破坏这些视图上的自动布局。
TigerCoding '04

该解决方案的问题在于,它仅消除了该视图拥有的约束(例如,其宽度和高度)。它不会删除诸如领先约束和最高约束之类的东西。
感性的

是的,有道理的话。尽管确实存在这些方法,但是它们并不能单独解决问题实际上正在询问的问题。
GSnyder

18

在Swift中:

import UIKit

extension UIView {

    /**
     Removes all constrains for this view
     */
    func removeConstraints() {

        let constraints = self.superview?.constraints.filter{
            $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
        } ?? []

        self.superview?.removeConstraints(constraints)
        self.removeConstraints(self.constraints)
    }
}

如果您的视图在“ superview!”中没有超级视图,则会崩溃。请将该部分包装成'if let
superview

1
这不会消除所有约束。约束采用2个受影响视图中最接近的父级,因此,如果您做出不共享该视图
超级

2
也许更正确的方法是比较身份if c.firstItem === self而不是强制转换as? UIView并检查是否与之相等==
user1244109 2016年

可能不应使用来自Apple doc的removeConstraints:// //在将来的版本中将不建议使用removeConstraints方法,应避免使用。而是使用+ [NSLayoutConstraint deactivateConstraints:]。
eonist

@eonist的答案是3年前。而不是批评,您应该对其进行编辑。
亚历山大·沃尔科夫

5

细节

  • Xcode 10.2.1(10E1001),Swift 5

import UIKit

extension UIView {

    func removeConstraints() { removeConstraints(constraints) }
    func deactivateAllConstraints() { NSLayoutConstraint.deactivate(getAllConstraints()) }
    func getAllSubviews() -> [UIView] { return UIView.getAllSubviews(view: self) }

    func getAllConstraints() -> [NSLayoutConstraint] {
        var subviewsConstraints = getAllSubviews().flatMap { $0.constraints }
        if let superview = self.superview {
            subviewsConstraints += superview.constraints.compactMap { (constraint) -> NSLayoutConstraint? in
                if let view = constraint.firstItem as? UIView, view == self { return constraint }
                return nil
            }
        }
        return subviewsConstraints + constraints
    }

    class func getAllSubviews(view: UIView) -> [UIView] {
        return view.subviews.flatMap { [$0] + getAllSubviews(view: $0) }
    }
}

用法

print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()

1
很好
eonist

2

Swift解决方案:

extension UIView {
  func removeAllConstraints() {
    var view: UIView? = self
    while let currentView = view {
      currentView.removeConstraints(currentView.constraints.filter {
        return $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
      })
      view = view?.superview
    }
  }
}

重要的是要遍历所有父母,因为两个元素之间的约束是共同祖先持有的,因此仅清除此答案中详细说明的超级视图还不够好,以后您可能会感到吃惊。


2

根据先前的答案(速览4)

当您不想爬网整个层次结构时,可以使用InstantConstraints。

extension UIView {
/**
 * Deactivates immediate constraints that target this view (self + superview)
 */
func deactivateImmediateConstraints(){
    NSLayoutConstraint.deactivate(self.immediateConstraints)
}
/**
 * Deactivates all constrains that target this view
 */
func deactiveAllConstraints(){
    NSLayoutConstraint.deactivate(self.allConstraints)
}
/**
 * Gets self.constraints + superview?.constraints for this particular view
 */
var immediateConstraints:[NSLayoutConstraint]{
    let constraints = self.superview?.constraints.filter{
        $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
        } ?? []
    return self.constraints + constraints
}
/**
 * Crawls up superview hierarchy and gets all constraints that affect this view
 */
var allConstraints:[NSLayoutConstraint] {
    var view: UIView? = self
    var constraints:[NSLayoutConstraint] = []
    while let currentView = view {
        constraints += currentView.constraints.filter {
            return $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
        }
        view = view?.superview
    }
    return constraints
}
}

2

我使用以下方法从视图中删除所有约束:

.h文件:

+ (void)RemoveContraintsFromView:(UIView*)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child;

.m文件:

+ (void)RemoveContraintsFromView:(UIView *)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child
{
    if (parent) {
        // Remove constraints between view and its parent.
        UIView *superview = view.superview;
        [view removeFromSuperview];
        [superview addSubview:view];
    }

    if (child) {
        // Remove constraints between view and its children.
        [view removeConstraints:[view constraints]];
    }
}

您也可以在我的博客上阅读此帖子,以更好地了解其幕后工作原理。

如果您需要更精细的控制,我强烈建议您切换到Masonry,这是一个功能强大的框架类,可在需要以编程方式正确处理约束时使用。


2
兄弟姐妹呢?
zrslv 2015年


1

与ObjectiveC

[self.superview.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
        if (constraint.firstItem == self || constraint.secondItem == self) {
            [self.superview removeConstraint:constraint];
        }
    }];
    [self removeConstraints:self.constraints];
}

0

您可以使用如下形式:

[viewA.superview.constraints enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
    if (constraint.firstItem == viewA || constraint.secondItem == viewA) {
        [viewA.superview removeConstraint:constraint];
    }
}];

[viewA removeConstraints:viewA.constraints];

基本上,这枚举了viewA的超级视图上的所有约束,并删除了与viewA相关的所有约束。

然后,第二部分使用viewA的约束数组从viewA除去约束。


1
该解决方案的问题在于,它仅删除了超级视图所拥有的约束。它不会删除影响视图的所有约束。(例如,您可以在超级视图的超级视图的超级视图上轻松设置约束,而使用此解决方案则无法删除该
约束

我刚刚更新了问题,以说明为什么此解决方案不起作用。这是最接近正确的,因为它与我上面提到的蛮力解决方案最接近。但是,此解决方案将错误地删除,[C7]并且将错误地不删除[C3,C4]
有意义

您可以设计爬网层次结构的方法。但是此解决方案已经足够好了,因为无论如何您都应该在即时超级视图之上添加约束。国际海事组织(IMO)
教徒

0

(截至2017年7月31日)

SWIFT 3

self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)

目标C

[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];

这是快速删除UIView上存在的所有约束的最简单方法。只需确保使用新的约束或之后的新框架添加回UIView =)


0

使用可重用序列

我决定以一种更“可重用”的方式进行处理。由于找到影响视图的所有约束是上述所有方面的基础,因此我决定实现一个自定义序列,将所有这些约束以及所有拥有的视图返回给我。

首先要做的是定义一个扩展ArraysNSLayoutConstraint回返影响特定视图的所有元素。

public extension Array where Element == NSLayoutConstraint {

    func affectingView(_ targetView:UIView) -> [NSLayoutConstraint] {

        return self.filter{

            if let firstView = $0.firstItem as? UIView,
                firstView == targetView {
                return true
            }

            if let secondView = $0.secondItem as? UIView,
                secondView == targetView {
                return true
            }

            return false
        }
    }
}

然后,我们在自定义序列中使用该扩展名,该扩展名返回影响该视图的所有约束以及实际拥有它们的视图(可以在视图层次结构中的任何位置)

public struct AllConstraintsSequence : Sequence {

    public init(view:UIView){
        self.view = view
    }

    public let view:UIView

    public func makeIterator() -> Iterator {
        return Iterator(view:view)
    }

    public struct Iterator : IteratorProtocol {

        public typealias Element = (constraint:NSLayoutConstraint, owningView:UIView)

        init(view:UIView){
            targetView  = view
            currentView = view
            currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
        }

        private let targetView  : UIView
        private var currentView : UIView
        private var currentViewConstraintsAffectingTargetView:[NSLayoutConstraint] = []
        private var nextConstraintIndex = 0

        mutating public func next() -> Element? {

            while(true){

                if nextConstraintIndex < currentViewConstraintsAffectingTargetView.count {
                    defer{nextConstraintIndex += 1}
                    return (currentViewConstraintsAffectingTargetView[nextConstraintIndex], currentView)
                }

                nextConstraintIndex = 0

                guard let superview = currentView.superview else { return nil }

                self.currentView = superview
                self.currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
            }
        }
    }
}

最后,我们声明一个扩展UIView以在一个简单的属性中公开所有影响它的约束,您可以使用简单的for-each语法对其进行访问。

extension UIView {

    var constraintsAffectingView:AllConstraintsSequence {
        return AllConstraintsSequence(view:self)
    }
}

现在我们可以迭代所有影响视图的约束,并使用它们来做我们想做的...

列出他们的标识符...

for (constraint, _) in someView.constraintsAffectingView{
    print(constraint.identifier ?? "No identifier")
}

停用它们...

for (constraint, _) in someView.constraintsAffectingView{
    constraint.isActive = false
}

或将其完全删除...

for (constraint, owningView) in someView.constraintsAffectingView{
    owningView.removeConstraints([constraint])
}

请享用!


0

迅速

以下UIView Extension将删除视图的所有Edge约束:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}

-1

这是从特定视图禁用所有约束的方法

 NSLayoutConstraint.deactivate(myView.constraints)

该解决方案的问题在于,它仅消除了该视图拥有的约束(例如,其宽度和高度)。它不会删除诸如领先约束和最高约束之类的东西。
感性的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.