I frequently run into this problem where I want my views arranged in one manner for portrait and a somewhat drastically different manner for landscape.
For a simplified example, consider the following:
Notice how in portrait the images are stacked vertically, but in landscape, they are side-by-side?
Of course, I can manually calculate the positions and sizes, but for more complicated views, this quickly becomes complex. Plus, I'd prefer to use Autolayout so there's less device-specific code (if any at all). But that seems like a lot of Autolayout code to write. Is there a way to do set up the constraints for this sort of stuff through the storyboard?
Auto Layout dynamically calculates the size and position of all the views in your view hierarchy, based on constraints placed on those views. For example, you can constrain a button so that it is horizontally centered with an Image view and so that the button's top edge always remains 8 points below the image's bottom.
To create constraints select the button and click the Align icon in the auto layout menu. A popover menu will appear, check both “Horizontal in container” and “Vertically in container” options to center the button on the screen. Then click the “Add 2 Constraints” button. Run the application.
Auto Layout is a constraint-based layout system designed for building dynamically sized user interfaces. It lets you create user interface layouts that dynamically adapt for all screen sizes without manually setting the frame of every view.
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.
IBOutlet
connections for NSLayoutConstraints
.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:
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() } }
@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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With