Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Auto layout - and how to royally mess it up

Tom Elliott
February 08, 2016

Auto layout - and how to royally mess it up

A tour of some of the more annoying Auto Layout issues encountered during development of the Screens for iOS SDK at Yext.

Tom Elliott

February 08, 2016
Tweet

More Decks by Tom Elliott

Other Decks in Programming

Transcript

  1. Auto Layout And How to Royally Mess It Up Tom

    Elliott / telliott.io @theotherelliott
  2. #1 in Digital Location Management Forbes Most Promising Companies (2014

    & 2015) Fortune's Best Places to Work (2014 & 2015).
  3. Requirements Support iOS 7 and 8 No third-party libraries Embed

    into any UI Distribute as framework (No storyboards or xib)
  4. Constraints Specify a relationship between the layout properties of two

    views Properties: position or size Comparison and may include a constant Vertical solution does not affect horizontal solution
  5. Programmatic Auto Layout Creating a Constraint [view addConstraint:[NSLayoutConstraint constraintWithItem:child1 attribute:NSLayoutAttributeCenterY

    relatedBy:NSLayoutRelationEqual toItem:child2 attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0] ];
  6. Programmatic Autolayout Creating multiple constraints - Visual Format Language [view

    addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[child1]-(20)-[child2]-|" options:kNilOptions metrics:@{} views:views ] ];
  7. Unsatisfiable Constraints Unable to simultaneously satisfy constraints. Probably at least

    one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand , refer to the documentation for the UIView property translatesAutoresizingMaskIntoCons traints) ( "<NSAutoresizingMaskLayoutConstraint:0x7fe709418da0 h=--& v=--& H:[UIImageView:0x7f e709434820(320)]>", "<NSLayoutConstraint:0x7fe70943b4b0 H:|-(0)-[UIImageView:0x7fe709434820] (Names: '|':UIView:0x7fe709434f40 )>", "<NSLayoutConstraint:0x7fe70943b500 H:[UIImageView:0x7fe709434820]-(0)-| (Names: '|':UIView:0x7fe709434f40 )>", "<NSLayoutConstraint:0x7fe709438470 'UIView-Encapsulated-Layout-Width' H:[UIView:0x 7fe709434f40(414)]>" )
  8. Unsatisfiable Constraints How to Break It Forget to set up

    your views for autolayout Autoresizing: predecessor to Auto Layout By default, all programmatically created views are configured for autoresizing compatibility Adds constraints that will mess yours up
  9. Unsatisfiable Constraints How Not to Break It Turn off autoresizing

    support subview.translatesAutoresizingMaskIntoConstraints = NO;
  10. Unsatisfiable Constraints How to Break It Limit your view to

    a fixed size @"H:|[green(==100)][blue(==100)]|"
  11. Unsatisfiable Constraints How to Break It Limit your view to

    a minimum/maxiumum size @"H:|[green(==100)][blue(<=100)]|"
  12. Debugging YXTUnsatisfiableConstraintsDetector Monitors the console for unsatisfiable constraint errors and

    provides runtime callbacks https://github.com/yext/YXTUnsatisfiableConstraintsDetector
  13. Ambiguous Layout Multiple solutions to constraints May not always be

    apparent in testing Test with: hasAmbiguousLayout Exercise with: exerciseAmbiguityInLayout
  14. Ambiguous Layout How Not to Break It Add a Deciding

    Constraint Spacer is a UIView used to support up to 300px @"H:|[leftView(==rightView)]" @"H:|[leftView(<=100)][spacer(==leftView@999)][rightView(<=100)]|"
  15. Crashes How to Break It Typos [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:

    @"H:|[one][two(==one)][three(==one)]|" options:0 metrics:nil views:views]]; [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"H:|[four(==one)][five(==one)][six(==one)]|" options:0 metrics:nil views:views]]; [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"H:|[seven(==one)][eigdt(==one)][nine(==one)]|" options:0 metrics:nil views:views]]; [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"H:|[star(==one)][zero(==one)][hash(==one)]|" options:0 metrics:nil views:views]];
  16. Crashes How to Break It Typos (part 2) Seven blocks

    of VFL [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"V:|[one][four(==one)][seven(==one)][star(==one)]|" options:0 metrics:nil views:views]]; [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"V:|[two(==one)][five(==one)][eight(==one)][zero(==one)]|" options:0 metrics:nil views:views]]; [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"V:|[three(==one)][six(==one)][nine(==one)][hash(==one)]|" options:0 metrics:nil views:views]];
  17. Crashes How Not to Break It @implementation PhoneRowView - (id)

    initWithLeft:(UIButton*)left middle:(UIButton*)middle right:(UIButton*)right { if(self = [super init]){ [self addSubview:left]; [self addSubview:middle]; [self addSubview:right]; NSDictionary *views = NSDictionaryOfVariableBindings(left,middle,right); // OMITTED: Make our left, right and middle views full-height (V:|[view]|) [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[left][middle(==left)][right(==left)]|" options:0 metrics:nil views:views]]; } return self; } @end
  18. Crashes How Not to Break It Four blocks of VFL

    at most PhoneRowView *row1 = [[PhoneRowView alloc] initWithLeft:digits[7] middle:digits[8] right:digits[9]]; PhoneRowView *row2 = [[PhoneRowView alloc] initWithLeft:digits[4] middle:digits[5] right:digits[6]]; PhoneRowView *row3 = [[PhoneRowView alloc] initWithLeft:digits[1] middle:digits[2] right:digits[3]]; PhoneRowView *row4 = [[[PhoneRowView alloc] initWithLeft:hash middle:digits[0] right:star]; // OMITTED: Prepare rows for Auto Layout, add to view // and make full width (H:|[view]|) NSDictionary *views = NSDictionaryOfVariableBindings(row1,row2,row3,row4); [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat: @"V:|[row1][row2(==row1)][row3(==row1)][row4(==row1)]|" options:0 metrics:nil views:views]];
  19. Crashes How to Break It Constraining to a superview too

    early - (id) init { // ... [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[self]|" options:0 metrics:nil views:@{@"self" : self}]];
  20. Avoid Adding/Removing Constraints Add constraints up-front in the init method

    Modify constraints in updateConstraints method Change properties rather than replacing constraints Example: Resizing the height of a view Two constraints: heightConstraintCollapsed and heightConstraintExpanded
  21. Avoid Adding/Removing Constraints Not ideal - (void) updateConstraints { if(self.isExpanded){

    [self removeConstraint:self.heightConstraintCollapsed]; [self addConstraint:self.heightConstraintExpanded]; } else { [self removeConstraint:self.heightConstraintExpanded]; [self addConstraint:self.heightConstraintCollapsed]; } [super updateConstraints]; }
  22. Avoid Adding/Removing Constraints Very Bad - (void) updateConstraints { [self

    removeConstraints:self.constraints]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[content]|" options:0 metrics:nil views:@{@"content" : self.content}]]; [self addConstraint:[NSLayoutConstraint constraintWithItem:self.content attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:self.expanded ? 300 : 20]] [super updateConstraints]; }
  23. Avoid Adding/Removing Constraints A Better Approach Modify constraint priorities -

    (void) updateConstraints { if(self.isExpanded){ self.heightConstraintCollapsed.priority = 1; self.heightConstraintExpanded.priority = 999; } else { self.heightConstraintCollapsed.priority = 999; self.heightConstraintExpanded.priority = 1; } [super updateConstraints]; }
  24. Avoid Adding/Removing Constraints The Best Approach Modify constraint constants -

    (void) updateConstraints { self.heightConstraint.constant = self.isExpanded ? 300 : 20; [super updateConstraints]; }
  25. Use Intrinsic Sizing // Specify width only - (CGSize) intrinsicContentSize

    { return CGSizeMake(500,UIViewNoIntrinsicMetric); } // Specify minimal size that satisfies constraints - (CGSize) intrinsicContentSize { [self systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; } // Prefer to be equal to or larger than intrinsic size [self setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal]; // Don't worry about fitting content [self setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
  26. Use UIStackLayout (iOS 9+) [green.heightAnchor constraintEqualToConstant:100].active = true; [green.widthAnchor constraintEqualToConstant:100].active

    = true; [blue.heightAnchor constraintEqualToConstant:100].active = true; [blue.widthAnchor constraintEqualToConstant:100].active = true; stack.axis = UILayoutConstraintAxisHorizontal; stack.distribution = UIStackViewDistributionEqualSpacing; stack.alignment = UIStackViewAlignmentCenter; [stack addArrangedSubview:green]; [stack addArrangedSubview:blue];