There are two underused features of Xcode's interface builder we can make use of here to make exactly this sort of thing a synch.
- You can create
IBOutlet
connections for NSLayoutConstraints
.
- You can hook up "outlet collections", which is an array of
IBOutlet
objects.
So with these two things in mind, the gist of what we want to do is create all of our autolayout constraints for one orientation, hook them all up in an outlet collection. Now, for each of these constraints, uncheck the "Installed" option on interface builder. Then make all of our outlet collections for another layout, and hook them up to another outlet collection. We can create as many of these layout groups as we want.
It's important to note that we will need a reference to any UI element which has constraints installed on it directly, and we will need a seperate outlet collection not just for each layout we want, but for each UI object which has constraints installed on it directly.
Let's take a look at the fairly simplified example from your question.
If you look in the constraints list on the left, you can see half of them are grayed-out. The grayed-out constraints are the landscape constraints. I created all of these, then unchecked "Installed" for each of them:
Meanwhile, the ungrayed, normal looking constraints are the portrait constraints. For these, I left them "Installed". It's completely unnecessary to leave any of them installed, but you will run into problems if you leave multiple sets installed (they most likely conflict).
Be sure not to check "Remove at build time" for any of these. We don't want the constraints "removed" at build time. This simply means the constraint isn't created at all (so we'll lose the reference to it). If we leave this check marked while we have an IBOutlet
to the constraint, Xcode will generate a warning:
Unsupported Configuration
Connection to placeholder constraint. Constraints marked as placeholders in IB should not have any connections since these constraints are not compiled into the document and will not exist at runtime.
Anyway, so now we need to hook up the constraints to an outlet so we can access them at run time.
Hold Ctrl and click and drag from one of the constraints to your source code file, just as you would connect any other UI element. On the dialog that pops up, choose Outlet Collection and a descriptive name:
Now hook up all of the other constraints that match that constraint group into the same outlet collection:
Once we've finished hooking up all of our constraints, it's just a matter of removing/adding them at the appropriate time.
For such a simple example as the scenario described in the question, we can override updateViewConstraints
with some code like this:
Swift
class ViewController: UIViewController {
@IBOutlet var landscapeConstraints: [NSLayoutConstraint]!
@IBOutlet var portraitConstraints: [NSLayoutConstraint]!
override func updateViewConstraints() {
let isPortrait = self.view.bounds.width < self.view.bounds.height
self.view.removeConstraints(self.portraitConstraints)
self.view.removeConstraints(self.landscapeConstraints)
self.view.addConstraints(isPortrait ? self.portraitConstraints : self.landscapeConstraints)
super.updateViewConstraints()
}
}
Objective-C
@interface ViewController()
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints;
@end
@implementation ViewController
- (void)updateViewConstraints {
BOOL isPortrait = self.view.bounds.size.width < self.view.boudns.size.height;
[self.view removeConstraints:self.portraitConstraints];
[self.view removeConstraints:self.landscapeConstraints];
[self.view addConstraints:(isPortrait ? self.portraitConstraints : self.landscapeConstraints)];
[super updateViewConstraints];
}
@end
We're not checking which set of constraints the view previously had, so just remove both of our constraint sets, and then add the appropriate set we want to use.
This is all the code we need to manage completely changing out an entire set of constraints on an object at run time. This allows to work in the interface builder to set all of our constraints up instead of having to do it programmatically (which I find a little more tedious and error-prone).
The end result? Very nice looking autorotation rearrangement without pulling your hair out getting the autolayout code done perfectly: