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

3 Things I Was Afraid to Ask About Auto Layout

3 Things I Was Afraid to Ask About Auto Layout

Have you ever wondered why we do things a certain way in Auto Layout? Are you worried it's too late to ask these questions? In this talk, we'll look at some common patterns in Auto Layout and do a deeper dive into how they came to be. This talk will indirectly cover some best practices for making your app UI scale to many devices.

Alex Figueroa

July 10, 2018
Tweet

Other Decks in Technology

Transcript

  1. 3 Things I Was Afraid to Ask About Auto Layout

    Alex Figueroa Wealthsimple @externconst
  2. View Requirement • A name (of course) • UIImageView •

    Aligned to all edges of its superview
  3. let imageView = UIImageView(image: seagullImage) view.addSubview(imageView) NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo:

    view.trailingAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) Note: view here is assumed to be an already created parent view of the label • Initialize a UIImageView with an image of a seagull ̼ • Constraint the leading, trailing, top, and bottom anchors to match its super view Implementation
  4. let imageView = UIImageView(image: seagullImage) view.addSubview(imageView) NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo:

    view.trailingAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) Note: view here is assumed to be an already created parent view of the label • Initialize a UIImageView with an image of a seagull ̼ • Constraint the leading, trailing, top, and bottom anchors to match its super view Implementation
  5. let imageView = UIImageView(image: seagullImage) view.addSubview(imageView) NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo:

    view.trailingAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) Note: view here is assumed to be an already created parent view of the label • Initialize a UIImageView with an image of a seagull ̼ • Constraint the leading, trailing, top, and bottom anchors to match its super view Implementation
  6. Classic Auto Layout warning 2018-07-09 00:59:00.773965-0400 PresentationPlayground[55474:1923914] [LayoutConstraints] 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) ( "<NSAutoresizingMaskLayoutConstraint:0x600000092d40 h=--& v=--& UIImageView:0x7f978ad04640.height == 1000 (active)>", "<NSLayoutConstraint:0x6000000926b0 V:|-(0)-[UIImageView:0x7f978ad04640] (active, names: '|':UIView:0x7f978ac05800 )>", "<NSLayoutConstraint:0x600000092700 UIImageView:0x7f978ad04640.bottom == UIView:0x7f978ac05800.bottom (active)>", "<NSLayoutConstraint:0x600000092e80 'UIView-Encapsulated-Layout-Height' UIView:0x7f978ac05800.height == 812 (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x600000092700 UIImageView:0x7f978ad04640.bottom == UIView:0x7f978ac05800.bottom (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
  7. Classic Auto Layout warning 2018-07-09 00:59:00.773965-0400 PresentationPlayground[55474:1923914] [LayoutConstraints] 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) ( "<NSAutoresizingMaskLayoutConstraint:0x600000092d40 h=--& v=--& UIImageView:0x7f978ad04640.height == 1000 (active)>", "<NSLayoutConstraint:0x6000000926b0 V:|-(0)-[UIImageView:0x7f978ad04640] (active, names: '|':UIView:0x7f978ac05800 )>", "<NSLayoutConstraint:0x600000092700 UIImageView:0x7f978ad04640.bottom == UIView:0x7f978ac05800.bottom (active)>", "<NSLayoutConstraint:0x600000092e80 'UIView-Encapsulated-Layout-Height' UIView:0x7f978ac05800.height == 812 (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x600000092700 UIImageView:0x7f978ad04640.bottom == UIView:0x7f978ac05800.bottom (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
  8. Classic Auto Layout warning 2018-07-09 00:59:00.773965-0400 PresentationPlayground[55474:1923914] [LayoutConstraints] 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) ( "<NSAutoresizingMaskLayoutConstraint:0x600000092d40 h=--& v=--& UIImageView:0x7f978ad04640.height == 1000 (active)>", "<NSLayoutConstraint:0x6000000926b0 V:|-(0)-[UIImageView:0x7f978ad04640] (active, names: '|':UIView:0x7f978ac05800 )>", "<NSLayoutConstraint:0x600000092700 UIImageView:0x7f978ad04640.bottom == UIView:0x7f978ac05800.bottom (active)>", "<NSLayoutConstraint:0x600000092e80 'UIView-Encapsulated-Layout-Height' UIView:0x7f978ac05800.height == 812 (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x600000092700 UIImageView:0x7f978ad04640.bottom == UIView:0x7f978ac05800.bottom (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
  9. let imageView = UIImageView(image: seagullImage) view.addSubview(imageView) NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo:

    view.trailingAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) The Fix • Unless manually setting frame, always disable translatesAutoresizingMasksIntoConstraints • Storyboards does this for free
  10. let imageView = UIImageView(image: seagullImage) imageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(imageView) NSLayoutConstraint.activate([

    imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) The Fix • Unless manually setting frame, always disable translatesAutoresizingMasksIntoConstraints • Storyboards does this for free
  11. • iPhone 5 was debuting the fall of 2012 •

    Auto Layout was introduced in iOS 6 • Before that, there was Springs and Struts Life before Auto Layout
  12. • Springs define the view's height and width • Struts

    are the view's distance to its container Springs and Struts
  13. • Springs define the view's height and width • Struts

    are the view's distance to its container Springs and Struts
  14. • Springs define the view's height and width • Struts

    are the view's distance to its container Springs and Struts
  15. • Springs define the view's height and width • Struts

    are the view's distance to its container Springs and Struts
  16. • Springs define the view's height and width • Struts

    are the view's distance to its container Springs and Struts
  17. • Enum in Objective-C Autoresizing Mask typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {

    UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 << 0, UIViewAutoresizingFlexibleWidth = 1 << 1, UIViewAutoresizingFlexibleRightMargin = 1 << 2, UIViewAutoresizingFlexibleTopMargin = 1 << 3, UIViewAutoresizingFlexibleHeight = 1 << 4, UIViewAutoresizingFlexibleBottomMargin = 1 << 5 };
  18. • During migration, lots of views were still using autoresizing

    masks (AM) • For backwards compatibility, UIKit bridges AM to Auto Layout • UIKit will create these constraints if translatesAutoResizingMaskIntoConstraints is left enabled • Your constraints will likely conflict! Why does this matter?
  19. Design Requirement • The image should minimize and maximize when

    the user taps on the preview • Respect the safe area too!
  20. View Requirement • Maintain an aspect ratio of 3:2 •

    Adjust the size of the preview to some percentage of the views size • Reuse the existing imageView
  21. Constraints Requirement • We defined the constraints to have all

    its edges aligned to its superview ImageView
  22. Constraints Requirement • We need the image view to fit

    in the bottom right corner with new constraints Image View
  23. Constraints Requirement Image View imageView.bottom = superView.bottom imageView.trailing = superView.trailing

    imageView.width = 0.4 * superView.width imageView.height = 1.5 * imageView.width
  24. Constraints Requirement let imageView = UIImageView(image: seagullImage) view.addSubview(imageView) NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo:

    view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), imageView.topAnchor.constraint(equalTo: view.topAnchor), imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ])
  25. Constraints Requirement leading = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor) trailing = imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor)

    top = imageView.topAnchor.constraint(equalTo: view.topAnchor) bottom = imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor) NSLayoutConstraint.activate([ leading, trailing, top, bottom ]) Note: constraints are assumed to be defined as properties on the VC already
  26. Constraints Requirement leading = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor) trailing = imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor)

    top = imageView.topAnchor.constraint(equalTo: view.topAnchor) bottom = imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor) width = imageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.4) height = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.5) NSLayoutConstraint.activate([ leading, trailing, top, bottom ])
  27. Minimization Animation UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive =

    false width.isActive = true height.isActive = true } • Deactivate the leading and top • Activate the new width and height constraints
  28. Minimization Animation UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive =

    false width.isActive = true height.isActive = true } • Deactivate the leading and top • Activate the new width and height constraints
  29. Minimization Animation UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive =

    false width.isActive = true height.isActive = true } • Deactivate the leading and top • Activate the new width and height constraints
  30. Animation Result... • Observe the frame change is abrupt •

    The frame is only updating after the animation finishes
  31. UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive = false width.isActive

    = true height.isActive = true } The Fix • Force any pending layout operations to finish • Capture the frame changes in the animation block (relative to parent view)
  32. view.layoutIfNeeded() UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive = false

    width.isActive = true height.isActive = true view.layoutIfNeeded() } The Fix • Force any pending layout operations to finish • Capture the frame changes in the animation block
  33. view.layoutIfNeeded() UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive = false

    width.isActive = true height.isActive = true view.layoutIfNeeded() } The Fix • Force any pending layout operations to finish • Capture the frame changes in the animation block
  34. - Apple Documentation for layoutIfNeeded Use this method to force

    the layout of subviews before drawing. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root.
  35. Layout Pass • After events are handled, the system begins

    updating the layout, display, and constraints • If a change is requested while an event is being handled, the view gets marked as needing a layout update • At the next update cycle, the system will execute all changes on the marked views
  36. Layout Pass • setNeedsLayout • Informs the system that a

    view needs to be updated on the next layout pass • layoutIfNeeded • Updates the view immediately if possible • Both indirectly call layoutSubviews
  37. What triggers setNeedsLayout? • Adding or removing a subview •

    Resizing a view • Rotation • Updating constraints
  38. What triggers setNeedsLayout? • Adding or removing a subview •

    Resizing a view • Rotation • Updating constraints
  39. Constraints to Position and Size ImageView imageView.top = superView.top imageView.trailing

    = superView.trailing imageView.leading = superView.leading imageView.bottom = superView.bottom
  40. view.layoutIfNeeded() UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive = false

    width.isActive = true height.isActive = true view.layoutIfNeeded() } Revisiting the fix • Start the layout pass with a fresh slate
  41. view.layoutIfNeeded() UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive = false

    width.isActive = true height.isActive = true view.layoutIfNeeded() } Revisiting the fix • Start the layout pass with a fresh slate • Update constraints which trigger frame* update via setNeedsLayout Note: Auto Layout modifies the alignment rect and not the frame
  42. view.layoutIfNeeded() UIView.animate(withDuration: 0.3) { leading.isActive = false top.isActive = false

    width.isActive = true height.isActive = true view.layoutIfNeeded() } Revisiting the fix • Start the layout pass with a fresh slate • Update constraints which trigger frame update via setNeedsLayout • Force a redraw so frame change can be animated and interpolated
  43. Another Auto Layout warning 2018-07-10 01:11:44.748866-0400 PresentationPlayground[16868:534664] [LayoutConstraints] 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. ( "<NSLayoutConstraint:0x60c00009c160 H:|-(0)-[UIView:0x7fec7a809030] (active, names: '|':UIView: 0x7fec77502890 )>", "<NSLayoutConstraint:0x60c00009c3e0 UIView:0x7fec7a809030.trailing == UIView:0x7fec77502890.trailing (active)>", "<NSLayoutConstraint:0x60c00009c520 UIImageView:0x7fec7a815d40.width == 30 (active)>", "<NSLayoutConstraint:0x60c00009c5c0 H:|-(12)-[UIImageView:0x7fec7a815d40] (active, names: '|':UIView: 0x7fec7a809030 )>", "<NSLayoutConstraint:0x60c00009c430 UITextField:0x7fec7788b600.width == 300 (active)>", "<NSLayoutConstraint:0x60c00009c340 H:[UIImageView:0x7fec7a815d40]-(12)-[UITextField:0x7fec7788b600] (active)>", "<NSLayoutConstraint:0x60c00009bfd0 UIButton:0x7fec7740f270'Send'.trailing == UIView:0x7fec7a809030.trailing - 12 (active)>", "<NSLayoutConstraint:0x60c00009ba30 H:[UITextField:0x7fec7788b600]-(12)-[UIButton:0x7fec7740f270'Send'] (active)>", "<NSLayoutConstraint:0x60c000096800 'UIView-Encapsulated-Layout-Width' UIView:0x7fec77502890.width == 375 (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x60c00009c430 UITextField:0x7fec7788b600.width == 300 (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
  44. Auto Layout Errors • There are three types: • Unsatisfiable

    Layouts • Ambiguous Layouts • Logic Errors • Hardest part is figuring out the error and reading Visual Format Language (VFL)
  45. The Auto Layout warning "<NSLayoutConstraint:0x60c00009c160 H:|-(0)-[UIView: 0x7fec7a809030] (active, names: '|':UIView:

    0x7fec77502890 )>", "<NSLayoutConstraint:0x60c00009c3e0 UIView: 0x7fec7a809030.trailing == UIView:0x7fec77502890.trailing (active)>", "<NSLayoutConstraint:0x60c00009c520 UIImageView: 0x7fec7a815d40.width == 30 (active)>", "<NSLayoutConstraint:0x60c00009c5c0 H:|-(12)-[UIImageView: 0x7fec7a815d40] (active, names: '|':UIView: 0x7fec7a809030 )>", "<NSLayoutConstraint:0x60c00009c430 UITextField: 0x7fec7788b600.width == 300 (active)>", "<NSLayoutConstraint:0x60c00009c340 H:[UIImageView: 0x7fec7a815d40]-(12)-[UITextField:0x7fec7788b600] (active)>", "<NSLayoutConstraint:0x60c00009bfd0 UIButton: 0x7fec7740f270'Send'.trailing == UIView: 0x7fec7a809030.trailing - 12 (active)>", "<NSLayoutConstraint:0x60c00009ba30 H:[UITextField: 0x7fec7788b600]-(12)-[UIButton:0x7fec7740f270'Send'] (active)>", "<NSLayoutConstraint:0x60c000096800 'UIView-Encapsulated- Layout-Width' UIView:0x7fec77502890.width == 375 (active)>"
  46. The Auto Layout warning "<NSLayoutConstraint:0x60c00009c160 H:|-(0)-[UIView: 0x7fec7a809030] (active, names: '|':UIView:

    0x7fec77502890 )>", "<NSLayoutConstraint:0x60c00009c3e0 UIView: 0x7fec7a809030.trailing == UIView:0x7fec77502890.trailing (active)>", "<NSLayoutConstraint:0x60c00009c520 UIImageView: 0x7fec7a815d40.width == 30 (active)>", "<NSLayoutConstraint:0x60c00009c5c0 H:|-(12)-[UIImageView: 0x7fec7a815d40] (active, names: '|':UIView: 0x7fec7a809030 )>", "<NSLayoutConstraint:0x60c00009c430 UITextField: 0x7fec7788b600.width == 300 (active)>", "<NSLayoutConstraint:0x60c00009c340 H:[UIImageView: 0x7fec7a815d40]-(12)-[UITextField:0x7fec7788b600] (active)>", "<NSLayoutConstraint:0x60c00009bfd0 UIButton: 0x7fec7740f270'Send'.trailing == UIView: 0x7fec7a809030.trailing - 12 (active)>", "<NSLayoutConstraint:0x60c00009ba30 H:[UITextField: 0x7fec7788b600]-(12)-[UIButton:0x7fec7740f270'Send'] (active)>", "<NSLayoutConstraint:0x60c000096800 'UIView-Encapsulated- Layout-Width' UIView:0x7fec77502890.width == 375 (active)>"
  47. Adding Identifiers • Identifiers can be added to constraints and

    layout guides • This can be done in Storyboards or progammatically leadingContstraint.identifier = "Leading Constraint" layoutGuide.identifier = "I'm a layout guide!"
  48. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  49. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  50. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  51. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  52. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  53. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  54. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  55. The Auto Layout warning "<NSLayoutConstraint:0x60800008e6f0 'Button Leading' H: [UITextField:0x7ff0f403da00]-(12)-[UIButton: 0x7ff0f2e07ce0'Send']

    (active)>", "<NSLayoutConstraint:0x60800008e6a0 'Button Trailing' UIButton:0x7ff0f2e07ce0'Send'.trailing == UIView: 0x7ff0f2f0b1a0.trailing - 12 (active)>", "<NSLayoutConstraint:0x60800008e150 'Container Leading' H:|- (0)-[UIView:0x7ff0f2f0b1a0] (active, names: '|':UIView: 0x7ff0f2f0c140 )>", "<NSLayoutConstraint:0x60800008e1a0 'Container Trailing' UIView:0x7ff0f2f0b1a0.trailing == UIView: 0x7ff0f2f0c140.trailing (active)>", "<NSLayoutConstraint:0x60800008e2e0 'Profile Image Leading' H:|-(12)-[UIImageView:0x7ff0f2f0a7a0] (active, names: '|':UIView:0x7ff0f2f0b1a0 )>", "<NSLayoutConstraint:0x60800008e240 'Profile Image Width' UIImageView:0x7ff0f2f0a7a0.width == 30 (active)>", "<NSLayoutConstraint:0x60800008e3d0 'TextField Leading' H: [UIImageView:0x7ff0f2f0a7a0]-(12)-[UITextField: 0x7ff0f403da00] (active)>", "<NSLayoutConstraint:0x60800008e380 'TextField Width' UITextField:0x7ff0f403da00.width == 300 (active)>", "<NSLayoutConstraint:0x60c00008e650 'UIView-Encapsulated- Layout-Width' UIView:0x7ff0f2f0c140.width == 375 (active)>"
  56. Why The Failure (WTFAutoLayout)? • Website that lets you view

    AL error logs visually • https://www.wtfautolayout.com
  57. Summary • Always disable translatesAutoResizingMaskIntoConstraints if you plan to add

    constraints to a view • Call layoutIfNeeded before and within an animation block for animating constraints • Use a methodical approach when debugging AL logs. Use identifiers!