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

Auto Layouters Anonymous

Auto Layouters Anonymous

I gave this talk at the March 18, 2014, CocoaHeadsNL meeting.

In an attempt to mediate a resolution of the troubled relationship that many members of our community have with Auto Layout, I talked about my experience with Auto Layout, the basics of Auto Layout and some of the more advanced stuff.

There were quite some movies in the presentation, which don't translate well to pdf. Sorry about that.

Thanks to my colleagues at Touchwonders (Dick, Tim, Lars, Jonah & Christian) and Chris Eidhof for providing feedback to an earlier version of this talk.

Thomas Visser

March 18, 2014
Tweet

More Decks by Thomas Visser

Other Decks in Programming

Transcript

  1. “I tried using Auto Layout, hated it, tried it again,

    loved it, used it for absolutely everything, that didn’t work out too well and now I try to use it where it makes sense.”
  2. Auto Layout • Uniform layout description for multiple orientations &

    UI idioms • Declarative UI definition • More flexible UI for easier localisation • Bi-directional UI support • Fun
  3. _horizontalSpacingConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_checkboxView]-(checkboxToLabelSpacing)-[_labelView]-|" options:0 metrics:@{@"checkboxToLabelSpacing": @(kHSCheckboxToLabelHorizontalSpacing)} views:views]; [self addConstraints:

    _horizontalSpacingConstraints]; _checkboxVerticalCenteringConstraint = [NSLayoutConstraint constraintWithItem:_checkboxView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]; [self addConstraint: _checkboxVerticalCenteringConstraint]; _labelVerticalCenteringConstraint = [NSLayoutConstraint constraintWithItem:_labelView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]; [self addConstraint: _labelVerticalCenteringConstraint]; ! _checkboxWidthConstraint = [NSLayoutConstraint constraintWithItem:_checkboxView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:kHSCheckboxSize.width]; _checkboxWidthConstraint.priority = UILayoutPriorityRequired; [_checkboxView addConstraint: _checkboxWidthConstraint]; _checkboxHeightConstraint = [NSLayoutConstraint constraintWithItem:_checkboxView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:kHSCheckboxSize.height]; _checkboxHeightConstraint.priority = UILayoutPriorityRequired; [_checkboxView addConstraint: _checkboxHeightConstraint]; _checkboxHeightToContentHeightConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(verticalPadding)-[_checkboxView]-(verticalPadding)-|" options:0 metrics:@{@"verticalPadding": @(kHSCheckboxVerticalPadding)}
  4. view.centerY = superview.centerY * 0.5 A Constraint centerY 0.5* centerY

    centerY Defines a relation between two view attributes
  5. view1.attribute1 = view2.attribute2 * multiplier + constant A Constraint typedef

    NS_ENUM(NSInteger, NSLayoutAttribute) { NSLayoutAttributeLeft = 1, NSLayoutAttributeRight, NSLayoutAttributeTop, NSLayoutAttributeBottom, NSLayoutAttributeLeading, NSLayoutAttributeTrailing, NSLayoutAttributeWidth, NSLayoutAttributeHeight, NSLayoutAttributeCenterX, NSLayoutAttributeCenterY, NSLayoutAttributeBaseline, NSLayoutAttributeNotAnAttribute = 0 };
  6. A Constraint typedef NS_ENUM(NSInteger, NSLayoutRelation) { NSLayoutRelationLessThanOrEqual = -1, NSLayoutRelationEqual

    = 0, NSLayoutRelationGreaterThanOrEqual = 1, }; view1.attribute1 = view2.attribute2 * multiplier + constant
  7. A Constraint enum { UILayoutPriorityRequired = 1000, UILayoutPriorityDefaultHigh = 750,

    UILayoutPriorityDefaultLow = 250, UILayoutPriorityFittingSizeLevel = 50, }; typedef float UILayoutPriority; @ priority view1.attribute1 = view2.attribute2 * multiplier + constant
  8. A Constraint @ priority view1.attribute1 = view2.attribute2 * multiplier +

    constant view.width = superview.width * 0.5 + 0.0 1:2 goes both ways
  9. view.width = superview.width * 0.5 + 0.0 A Constraint @

    priority view1.attribute1 = view2.attribute2 * multiplier + constant goes both ways
  10. view.width = superview.width * 0.5 + 0.0 A Constraint superview.width

    = (view.width - 0.0)/0.5 @ priority view1.attribute1 = view2.attribute2 * multiplier + constant goes both ways
  11. Layout Engine • Constraints • Intrinsic Content Sizes Meet as

    many constraints and intrinsic content sizes as possible, keeping constraint priority, content hugging and compression resistance into account Inputs Process
  12. Meet as many constraints and intrinsic content sizes as possible,

    keeping constraint priority, content hugging and compression resistance into account Layout Engine • Constraints • Intrinsic Content Sizes Inputs Process
  13. Layout Engine Inputs • Constraints • Intrinsic Content Sizes Process

    Linear Algebra Output • No layout
 or • Multiple Layouts
 or • A Single Layout
  14. Layout Engine Unsatisfiable constraints Ambiguous layout • Constraints • Intrinsic

    Content Sizes Linear Algebra ✔ Inputs Process Output • No layout
 or • Multiple Layouts
 or • A Single Layout
  15. A Fully Specified Layout • At least two constraints or

    content size specification per axis ! • No contradicting constraints ! • Result: Every view has a valid frame
  16. Animations • Frame changes can be animated. • Frames change

    by changing constraints. • Constraint changes can be animated.
  17. Animations • Frame changes can be animated. • Frames change

    by changing constraints. • Constraint changes can be animated. UIView
  18. - (void)pan:(UIPanGestureRecognizer*)sender { if (sender.state == UIGestureRecognizerStateBegan) { ! _grabbedView

    = [self.view hitTest:[sender locationInView:self.view] withEvent:nil]; ! } else if (sender.state == UIGestureRecognizerStateChanged) { ! CGPoint trans = [sender translationInView: self.view]; _grabbedView.center = CGPointMake(_grabbedView.center.x+trans.x, _grabbedView.center.y+trans.y); [sender setTranslation:CGPointZero inView:self.view]; } else if (sender.state == UIGestureRecognizerStateEnded) { ! [_grabbedView setNeedsLayout]; [UIView animateWithDuration:1.0 animations:^{ [_grabbedView layoutIfNeeded]; }]; ! } }
  19. Animation Caveats • Scope of [view layoutIfNeeded] is fuzzy •

    Anchor point determined by Auto Layout (CGAffineTransformScale )
  20. Animation Caveats • “Auto Layout is animating changes that I

    don’t want to animate!?” • “Auto Layout is not animating changes that I do want to animate!?”
  21. >= 0 @ 1000 >= 0 @ 1000 >= 0

    @ 1000 ! ! @ 500 @ 500
  22. Auto Layout is not free (as in no-op) 
 [self.window

    addConstraints: allMyConstraints]; ❌
  23. 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 translatesAutoresizingMaskIntoConstraints) ( "<NSLayoutConstraint:0x10927a090 H:[UIView:0x10927be30(75)]>", "<NSLayoutConstraint:0x10922bd40 H:|-(NSSpace(20))-[UIView:0x10927be30] (Names: '|':UIView:0x109269cf0 )>", "<NSLayoutConstraint:0x109275f50 H:[UIView:0x10927be30]-(200)-[UIView:0x10927c970]>", "<NSLayoutConstraint:0x10927b6a0 H:[UIView:0x10927c970]-(20)-| (Names: '|':UIView:0x109269cf0 )>", "<NSLayoutConstraint:0x10927eb00 H:|-(NSSpace(20))-[UIView:0x109269cf0] (Names: '|':UIView:0x109267470 )>", "<NSLayoutConstraint:0x10927eb50 H:[UIView:0x109269cf0]-(NSSpace(20))-| (Names: '|':UIView:0x109267470 )>", "<NSAutoresizingMaskLayoutConstraint:0x10930ef10 h=-&- v=-&- UIView:0x109267470.width == UIWindow:0x10930a3e0.width>", "<NSAutoresizingMaskLayoutConstraint:0x10930fe60 h=--- v=--- H:[UIWindow:0x10930a3e0(320)]>" ) ! Will attempt to recover by breaking constraint <NSLayoutConstraint:0x10927a090 H:[UIView:0x10927be30(75)]> ! Break on objc_exception_throw to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
  24. // Debugging ! /* Everything in this section should be

    used in debugging only, never in shipping code. These methods may not exist in the future - no promises. */ @interface UIView (UIConstraintBasedLayoutDebugging) ! /* This returns a list of all the constraints that are affecting the current location of the receiver. The constraints do not necessarily involve the receiver, they may affect the frame indirectly. Pass UILayoutConstraintAxisHorizontal for the constraints affecting [self center].x and CGRectGetWidth([self bounds]), and UILayoutConstraintAxisVertical for the constraints affecting[self center].y and CGRectGetHeight([self bounds]). */ - (NSArray *)constraintsAffectingLayoutForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0); ! /* If there aren't enough constraints in the system to uniquely determine layout, we say the layout is ambiguous. For example, if the only constraint in the system was x = y + 100, then there are lots of different possible values for x and y. This situation is not automatically detected by UIKit, due to performance considerations and details of the algorithm used for layout. The symptom of ambiguity is that views sometimes jump from place to place, or possibly are just in the wrong place. -hasAmbiguousLayout runs a check for whether there is another center and bounds the receiver could have that could also satisfy the constraints. -exerciseAmbiguousLayout does more. It randomly changes the view layout to a different valid layout. Making the UI jump back and forth can be helpful for figuring out where you're missing a constraint. */ - (BOOL)hasAmbiguousLayout NS_AVAILABLE_IOS(6_0); - (void)exerciseAmbiguityInLayout NS_AVAILABLE_IOS(6_0); @end