Remove all constraints affecting a UIView

2020-02-16 06:02发布

I have a UIView which is placed on the screen via several constraints. Some of the constraints are owned by the superview, others are owned by other ancestors (e.g. perhaps the view property of a UIViewController).

I want to remove all of these old constraints, and place it somewhere new using new constraints.

How can I do this without creating an IBOutlet for every single constraint and having to remember which view owns said constraint?

To elaborate, the naive approach would be to create a bunch of IBOutlets for each of the constraints, and would then involve calling code such as:

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

The problem with this code is that even in the simplest case, I would need to create 2 IBOutlets. For complex layouts, this could easily reach 4 or 8 required IBOutlets. Furthermore, I would need to ensure that my call to remove the constraint is being called on the proper view. For example, imagine that myViewsLeftConstraint is owned by viewA. If I were to accidentally call [self.view removeConstraint:self.myViewsLeftConstraint], nothing would happen.

Note: The method constraintsAffectingLayoutForAxis looks promising, but is intended for debugging purposes only.


Update: Many of the answers I am receiving deal with self.constraints, self.superview.constraints, or some variant of those. These solutions won't work since those methods return only the constraints owned by the view, not the ones affecting the view.

To clarify the problem with these solutions, consider this view hierarchy:

  • Grandfather
    • Father
      • Me
        • Son
        • Daughter
      • Brother
    • Uncle

Now imagine we create the following constraints, and always attach them to their nearest common ancestor:

  • C0: Me: same top as Son (owned by Me)
  • C1: Me: width = 100 (owned by Me)
  • C2: Me: same height as Brother (owned by Father)
  • C3: Me: same top as Uncle (owned by Grandfather)
  • C4: Me: same left as Grandfather (owned by Grandfather)
  • C5: Brother: same left as Father (owned by Father)
  • C6: Uncle: same left as Grandfather (owned by Grandfather)
  • C7: Son: same left as Daughter (owned by Me)

Now imagine we want to remove all constraints affecting Me. Any proper solution should remove [C0,C1,C2,C3,C4] and nothing else.

If I use self.constraints (where self is Me), I will get [C0,C1,C7], since those are the only constraints owned by Me. Obviously it wouldn't be enough to remove this since it is missing [C2,C3,C4]. Furthermore, it is removing C7 unnecessarily.

If I use self.superview.constraints (where self is Me), I will get [C2,C5], since those are the constraints owned by Father. Obviously we cannot remove all these since C5 is completely unrelated to Me.

If I use grandfather.constraints, I will get [C3,C4,C6]. Again, we cannot remove all of these since C6 should remain intact.

The brute force approach is to loop over each of the view's ancestors (including itself), and seeing if firstItem or secondItem are the view itself; if so, remove that constraint. This will lead to a correct solution, returning [C0,C1,C2,C3,C4], and only those constraints.

However, I'm hoping there is a more elegant solution than having to loop through the entire list of ancestors.

15条回答
欢心
2楼-- · 2020-02-16 06:51

The easier and efficient approach is to remove the view from superView and re add as subview again. this causes all the subview constraints get removed automagically.

查看更多
Summer. ? 凉城
3楼-- · 2020-02-16 06:52

This approach worked for me:

@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

After it's done executing your view remains where it was because it creates autoresizing constraints. When I don't do this the view usually disappears. Additionally, it doesn't just remove constraints from superview but traversing all the way up as there may be constraints affecting it in ancestor views.


Swift 4 Version

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
    }
}
查看更多
够拽才男人
4楼-- · 2020-02-16 06:54

You can remove all constraints in a view by doing this:

self.removeConstraints(self.constraints)

EDIT: To remove the constraints of all subviews, use the following extension in Swift:

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}
查看更多
Rolldiameter
5楼-- · 2020-02-16 06:54

Based on previous answers (swift 4)

You can use immediateConstraints when you don't want to crawl entire hierarchies.

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
}
}
查看更多
家丑人穷心不美
6楼-- · 2020-02-16 06:54

This is the way to disable all constraints from a specific view

 NSLayoutConstraint.deactivate(myView.constraints)
查看更多
我欲成王,谁敢阻挡
7楼-- · 2020-02-16 06:56

Details

  • Xcode 10.2.1 (10E1001), Swift 5

Solution

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

Usage

print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()
查看更多
登录 后发表回答