Auto Layout: iOS 8

It’s been a pretty exciting summer as far as Auto Layout goes. I’m busy going over material so I can get some dates and work estimates to Trina on a possible 3rd edition of Auto Layout. Between Swift, OSX, iOS 8, and whatever is going to be announced in September, it looks like a particularly interesting place to be.

I can’t tell you how thrilled I am about active, the property that self-installs and removes constraints to their natural destination. I submitted my first radar on this back before the first edition was even published. Now with the third edition, I suppose I’ll be all “Yeah, that’s my radar baby!”

The long iOS 8 summer first gave us left and right layout guides, a la the top and bottom guides we first encountered in iOS 7 but they soon disappeared and have since been replaced by a suite of Margin-based layout attributes:

    NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),

Comments and properties in the UIView header file indicate a layout tree of margins. I think it’s fair to assume this ties into the adaptive application that runs properly both on full-screen as well as side-by-side.

 -layoutMargins returns a set of insets from the edge of the view's bounds that denote a default spacing for laying out content.
 If preservesSuperviewLayoutMargins is YES, margins cascade down the view tree, adjusting for geometry offsets, so that setting the left value of layoutMargins on a superview will affect the left value of layoutMargins for subviews positioned close to the left edge of their superview's bounds
 If your view subclass uses layoutMargins in its layout or drawing, override -layoutMarginsDidChange in order to refresh your view if the margins change.
@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0);
@property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0); // default is NO - set to enable pass-through or cascading behavior of margins from this view’s parent to its children
- (void)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0);

Unfortunately, the material from WWDC is light on details and these updates didn’t take place until fairly recent betas. Got any insights into how this material will play out? I’d love to hear it. (And thanks in advance!)


  • They’ve also added support for layout margins in IB, and I suspect this is how people will first learn about them. People will see a blizzard of mysterious warnings that they have the “prefer relative margin” property enabled on a layout constraint, which is supported on iOS8 but not iOS7.

    I think layoutMargins make a lot of sense for certain kinds of views, as a convenience to produce a more standard and aesthetic use of whitespace. But having them enabled by default and set to {8,8,8,8} by default will cause more trouble then help for most people.

    Also, I think it’s a bug that they default to certain size (8pt) but there doesn’t seem to be a constant that lets you access that value programmatically. So if you need to know it for your own layout calculations (e.g., when calculating the intrinsicContentSize of a composite view), then you’re stuck.

  • One thing I just discovered is that they are set to 0, 16, 0, 16 in a set of ViewControllers I created IB. When I then changed to use a UIPageViewController instead of a UINavigationController the insets are set to 0,0,0,0 somewhere in the UIViewController hierarchy. Since IB is controlling the layout relative to the layout margins there doesn’t seem to be a way to get what I see in IB to reflect in the running app.

    I have tried setting the margins myself but they are always reset somewhere during constraint resolution. I changed the base UIView class to one of my own that just had the method layoutMarginsDidChange and it is getting set in UIViewController _setContentOverlayInsets:. Since that is an internal/private method there is no way for me to figure out what they are basing the 0,0,0,0 on.
    Any thoughts on that?

  • Sigh. It never fails but when I ask someone for help I figure it out. My problem was that I had constructs a UIPageViewController as my root view controller. It contained an instance of UIPageViewController that it used to control everything. When I changed back to UIViewController things worked out.