108

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.

Senseful
  • 86,719
  • 67
  • 308
  • 465
  • How about you put an identifier to all the constraints you want to remove ? This way you do not need to keep an outlet for them. – nsuinteger Apr 12 '16 at 08:24

16 Answers16

84

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
    }
}
Community
  • 1
  • 1
marchinram
  • 5,698
  • 5
  • 47
  • 59
  • 3
    This should be the official answer. – Jason Crocker Jul 24 '15 at 12:22
  • why do you have self.translatesAutoresizingMaskIntoConstraints = YES; ? you literally never want this for views you are setting up with constraints? – lensovet Aug 14 '15 at 04:27
  • 6
    I'm removing the constraints, so I want to use the autoresize constraints after I remove the other constraints to hold it in place, without that line in my code the view would disappear – marchinram Aug 15 '15 at 05:22
  • I needed to *log* all constraints on a view for debugging purposes and I was able to modify this answer slightly to do that. +1 – chiliNUT May 04 '16 at 15:27
  • This would incorrectly remove `C7`. However, it should be easy to fix if you remove `[self removeConstraints:self.constraints];` and change `UIView *superview = self.superview;` to `UIView *superview = self;`. – Senseful Apr 04 '18 at 06:27
  • Why you did - self.translatesAutoresizingMaskIntoConstraints = YES;? That's completely blow up my console log with errors until i removed it. You need to set it to false if you work with constraints in code. – Evgeniy Kleban Jul 16 '19 at 12:12
  • In my case if I set self.translatesAutoresizingMaskIntoConstraints = false afterwards and add new constraints it works. – Luchi Parejo Alcazar Mar 25 '21 at 08:20
  • Absolutely fantastic. Thank you, almost 8 years later. – jcodes Jan 22 '22 at 00:06
60

The only solution I have found so far is to remove the view from its superview:

[view removeFromSuperview]

This looks like it removes all constraints affecting its layout and is ready to be added to a superview and have new constraints attached. However, it will incorrectly remove any subviews from the hierarchy as well, and get rid of [C7] incorrectly.

Senseful
  • 86,719
  • 67
  • 308
  • 465
  • You could use the firstItem and secondItem properties of the constraints to check if they apply to your view and loop through the view hierarchy to find all of them. I think removing and re-adding is a better solution, though. I'd be interested to see your use case since it seems like this is a very bad thing to be doing. Your constraints could easily become completely invalid since other views may be relying on those constraints to lay out properly. – bjtitus Jun 25 '14 at 21:53
  • @bjtitus: Good point. However, in this specific case, I am repositioning a view which has nothing dependent on it. It uses other views in order to know where to be placed, and there are no views that use it in order to know where to be placed. – Senseful Jun 25 '14 at 22:04
  • Not the only solution, but very simple. In my use case I have just removed the view from superview and then re-added it later. – Marián Černý Mar 29 '18 at 15:21
58

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)
    }
}
denis_lor
  • 6,212
  • 4
  • 31
  • 55
emdog4
  • 1,975
  • 3
  • 20
  • 25
  • 27
    The problem with this solution is that it removes only the constraints that this view owns (e.g. its width and height). It does not remove things such as the leading and top constraints. – Senseful Oct 06 '14 at 18:28
  • 2
    I haven't verified your comment above. However, I believe this would work the same as or better than above, [NSLayoutConstraint deactivateConstraints:self.constraints]; – emdog4 Oct 08 '14 at 15:27
  • 2
    That would suffer from the same problem as the original solution. `self.constraints` only returns the constraints that `self` owns, not all the constraints that affect `self`. – Senseful Oct 08 '14 at 19:18
  • 1
    I understand what you are saying now. In my *answer*, I am removing the constraints from the UITableViewCell's contentView. The method updateConstraints would then add constraints for all subviews and reset the layout. In my first *comment* above, I should have typed self.view.constraints. Both self.view and self.contentView are at the top of the view hierarchy. Setting translatesAutoresizingMasksToConstraints = YES on self.view or self.contentView is a bad idea. ;-). I don't like calls to addSubview: in my updateConstraints method, removing the view from the hierarchy seems unnecessary imo. – emdog4 Oct 09 '14 at 00:25
  • I just updated the question to show why a solution like this won't work for what I'm looking to do. Assuming you are within a UITableViewCell the equivalent of contentView is Grandfather. Therefore your solution would incorrectly remove `[C6]`, and incorrectly not remove `[C0,C1,C2]`. – Senseful Feb 10 '15 at 19:16
  • In my case, it no remove any constraints. – andy shih Nov 04 '15 at 09:14
  • this is totally wrong, as explained by @Senseful – Fattie Feb 03 '23 at 00:54
26

There are two ways of on how to achieve that according to Apple Developer Documentation

1. NSLayoutConstraint.deactivateConstraints

This is a convenience method that provides an easy way to deactivate a set of constraints with one call. The effect of this method is the same as setting the isActive property of each constraint to false. Typically, using this method is more efficient than deactivating each constraint individually.

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

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2. UIView.removeConstraints (Deprecated for >= iOS 8.0)

When developing for iOS 8.0 or later, use the NSLayoutConstraint class’s deactivateConstraints: method instead of calling the removeConstraints: method directly. The deactivateConstraints: method automatically removes the constraints from the correct views.

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

// Usage
yourView.removeConstraints(yourView.constraints)

Tips

Using Storyboards or XIBs can be such a pain at configuring the constraints as mentioned on your scenario, you have to create IBOutlets for each ones you want to remove. Even so, most of the time Interface Builder creates more trouble than it solves.

Therefore when having very dynamic content and different states of the view, I would suggest:

  1. Creating your views programmatically
  2. Layout them and using NSLayoutAnchor
  3. Append each constraint that might get removed later to an array
  4. Clear them every time before applying the new state

Simple Code

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

References

  1. NSLayoutConstraint
  2. UIView
E-Riddie
  • 14,660
  • 7
  • 52
  • 74
  • 2
    Be careful. Views that specify an intrinsic content size will have NSContentSizeLayoutConstraint added automatically by iOS. Removing this by accessing the normal yourView.constraints property will break autolayout on those views. – TigerCoding Apr 06 '17 at 00:39
  • 1
    The problem with this solution is that it removes only the constraints that this view owns (e.g. its width and height). It does not remove things such as the leading and top constraints. – Senseful Mar 22 '18 at 22:05
  • Yeah, what Senseful said. Although these methods do exist, they do not on their own solve the problem the question is actually asking about. – GSnyder Apr 30 '18 at 01:32
19

In 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)
    }
}
zombie
  • 5,069
  • 3
  • 25
  • 54
Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
  • This will crash if your view has no superview in 'superview!'. Please wrap the part into an 'if let superview = superview' :) – Bersaelor Mar 20 '16 at 11:12
  • 1
    This will not remove all constraints. Constraints take the nearest parent of the 2 affected views, so if you make a constraint that does not share the superview of this view, that constraint will not be removed... – vrwim Jul 27 '16 at 13:25
  • 2
    perhaps more correct would be to compare identity `if c.firstItem === self` instead of casting `as? UIView` and check equality with `==` – user1244109 Nov 02 '16 at 14:43
  • Should probably not use removeConstraints: from apple doc: // The removeConstraints method will be deprecated in a future release and should be avoided.  Instead use +[NSLayoutConstraint deactivateConstraints:]. – Sentry.co May 05 '18 at 11:55
  • @eonist the answer is 3 years ago. Instead of criticism you should edit it. – Alexander Volkov Jun 05 '18 at 09:38
  • @Alexander Volkov True that. Just didn't have time ;) – Sentry.co Jun 05 '18 at 09:46
6

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()
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
4

Swift

Following UIView Extension will remove all Edge constraints of a view:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}
Gurjit Singh
  • 1,723
  • 21
  • 27
3

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.

Hashem Aboonajmi
  • 13,077
  • 8
  • 66
  • 75
2

I use the following method to remove all constraints from a view:

.h file:

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

.m file:

+ (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]];
    }
}

You can also read this post on my blog to better understand how it works behind the hood.

If you need more granular control, I'd strongly advise switching to Masonry, a powerful framework class you could use whenever you need to properly handle constraints programmatically.

Darkseal
  • 9,205
  • 8
  • 78
  • 111
2

A Swift solution:

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

It's important to go through all the parents, since the constraints between two elements are holds by the common ancestors, so just clearing the superview as detailed in this answer is not good enough, and you might end up having bad surprise later on.

Community
  • 1
  • 1
Guig
  • 9,891
  • 7
  • 64
  • 126
2

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
}
}
Sentry.co
  • 5,355
  • 43
  • 38
1

With 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];
}
Giang
  • 2,384
  • 2
  • 25
  • 26
1

Using a Reusable Sequence

I decided to approach this in a more 'reusable' way. Since finding all constraints affecting a view is the basis for all of the above, I decided to implement a custom sequence that returns them all for me, along with the owning views.

First thing to do is define an extension on Arrays of NSLayoutConstraint that returns all elements affecting a specific view.

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

We then use that extension in a custom sequence that returns all constraints affecting that view, along with the views that actually own them (which can be anywhere up the view hierarchy)

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

Finally we declare an extension on UIView to expose all the constraints affecting it in a simple property that you can access with a simple for-each syntax.

extension UIView {

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

Now we can iterate all constraints affecting a view and do what we want with them...

List their identifiers...

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

Deactivate them...

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

Or remove them entirely...

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

Enjoy!

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
0

You could use something like this:

[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];

Basically, this is enumerates over all the constraints on the superview of viewA and removes all of the constraints that are related to viewA.

Then, the second part removes the constraints from viewA using the array of viewA's constraints.

Tristan
  • 21
  • 1
  • 2
  • 1
    The problem with this solution is that it removes only the constraints that are owned by the superview. It does not remove all constraints affecting the view. (E.g. you could have just as easily set up constraints on the superview's superview's superview, which wouldn't be removed using this solution.) – Senseful Feb 02 '15 at 22:06
  • I just updated the question to show why this solution will not work. This is the closest to being correct, since it's closest to the brute force solution I mention above. However, this solution will incorrectly remove `[C7]` and will incorrectly not remove `[C3,C4]`. – Senseful Feb 10 '15 at 19:21
  • you could design the method to crawl up the hierarchy. But this solution is good enough, since you should add constraints above immediate superview anyway. IMO – Sentry.co May 05 '18 at 11:46
0

(As of July 31, 2017)

SWIFT 3

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

Objective C

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

This is the easiest way to quickly remove all constraints that exist on a UIView. Just be sure to add the UIView back with it's new constraints or new frame afterwards =)

BennyTheNerd
  • 3,930
  • 1
  • 21
  • 16
-1

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

 NSLayoutConstraint.deactivate(myView.constraints)
user2501116
  • 119
  • 1
  • 4
  • The problem with this solution is that it removes only the constraints that this view owns (e.g. its width and height). It does not remove things such as the leading and top constraints. – Senseful Mar 22 '18 at 22:04