Disposable UI with the Interface Builder and Autolayout

Bohdan Orlov

iOS Developer @Badoo

Reusable UI

  • customisable
  • written in a code
  • very generic
  • built for the future
  • requires a lot of time

Disposable UI

  • not reusable
  • made in the Interface Builder
  • very specific
  • built to be scrapped
  • fast to build

Disposable UI 

  • not all tasks can be done in the IB
  • logic is spread between UI files and code
  • Its fast - less code has to be written.
  • less effort put to IB files so its easier to say "good bye"
  • One glance to IB file gives general idea of UI
  • able to achieve anything
  • everything in one place
  • code has to be written
  • code has to be supported
  • code will be scrapped and you will feel bad, its only matter of time
  • UI is "invisible" until whole code is read

In a code

In the IB

Building disposable UI in the IB

  • Reasons to use Autolayout
  • Common issues adopting Autolayout in the IB
  • Getting the most of IB using Size Classes
  • Principle of least astonishment (surprise)  using the IB
  • IB tips and tricks
  • Working with Autolayout in a code
  • Debugging Autolayout

Agenda

Reasons to use Autolayout

  • We can't hardcode frames in the IB anymore
  • We have system wide font settings (Dynamic Type)
  • Internationalisation (right to left)
  • Layout is expected to be adaptive (more than a simple stretching)
  • Autosized cells (if performance is acceptable)
  • WatchKit apps require Storyboards. 

To memory of -setFrame: and CGRectDivide()

Common issues adopting Autolayout in the IB

  • Ambiguous constraints 
  • Constraint priorities
  • Constraints to Margins
  • Equal space between views
  • Views group centring
  • Intrinsic content size
  • Multiline UILabel
  • Content hugging priority
  • Content compression resistance priority

Ambiguous constraints 

  • Don't press "Add Missing constraints" button
  • understand the reason
  • add constraints manually

Red circle:

Yellow triangle:

  • "Update frames" is our choice
  • Other options might update your constraints in unexpected way

Getting rid of:

Ambiguous constraints 

Adding constaraints manually

There are multiple ways to achieve same result with different constraints

Inequality constraint ambiguity

We can resolve this by adding additional constraints with priority less than required.

For example we can say:

View.width = width @ 750

Constraint priorities

static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000;
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750;
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; 

// the priority level with which the view wants to conform to the target size.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50; 
  • Its possible to have multiple constraint for same views/attributes
  • First constraint is not required thus if there is not enough space it won't be satisfied

Constraints to Margins

Equal space between views

For iOS 9+ we should use UIStackView

Constraints can be setup only on view attributes, but not other constraints.

We have to use dummy spacing views, with equal width constraint and 0 leading/trailing to neighbours

Views group centring

For iOS 9+ we should use UIStackView

Constraints can be setup only on view attributes, but not other constraints.

We have to use dummy container view

Intrinsic content size

  • Content-defined size which allows to skip setting explicit constraint for width and/or height
  • UILabel and UIButton have intrinsic content size for both width and height
  •  Override for custom/frame based views, invalidate it when appropriate

  • You can use UIViewNoIntrinsicMetric for width or height

  • Intrinsic content size of a view refers to its alignment rect, not to its frame.

Multiline UILabel

  • The intrinsic content size of multiline UILabel depends on the width of the lines, which is yet to be determined when solving the constraints.

- (void)layoutSubviews { 
    [super layoutSubviews]; 
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width; 
    [super layoutSubviews]; 
}
  • preferredMaxLayoutWidth, specifies the maximum line width for calculating the intrinsic content size.
  • Supposed to work without extra code in iOS 8+

Content hugging priority

Content compression resistance priority

  • Only take effect for views which define an intrinsic content size.
  • Translated into constraints implicitly
H:[label(<=100@250)]
V:[label(<=30@250)]
H:[label(>=100@750)]
V:[label(>=30@750)]

Getting the most of IB using

Size Classes

Size Classes depends on:

  • device
  • orientation
  • special cases like split view

<- iPhone 6 Plus is exception

iPhones' Size Classes

Selecting a Size Class in

Interface Builder

 

  • Changing size or position of views
  • Adding or removing views

  • Adding or removing constraints

  • Changing the font in labels, fields, text views, and buttons

Changes available for different size classes

Principle of least astonishment  using the IB

Counterexamples

 

Principle of least astonishment  using the IB

Examples

 

Dos:

  • Put dummy values into labels/buttons
  • Put dummy background color
  • Put dummy image into UIViews
  • Use IBDesignable and IBInspectable

Don'ts:

  • Don't use User Defined Runtime Attributes
  • Don't override values in code with same value.
  • Avoid removing views at runtime, prefer deactivating constraints

Principle of least astonishment  using the IB

IB tips and tricks

SHIFT + OPT + LMB

SHIFT + CTRL + LMB

IB tips and tricks

+ OPT 

Top level objects in Scene Dock

Drag with CTRL to view

<+ SHIFT

to add multiple

^ LMB + LMB 

IB tips and tricks

@IBInspectable / @IBDesignable

IB tips and tricks

Simulated metrics

Strong Outlets (again)

Сanvas configuration

Multiple nav bar items

IB tips and tricks

Сanvas configuration

Multiple nav bar items

Working with Autolayout

in a code

  • Don't work with Autolayout in code
  • Don't add constraints in code
  • Don't be naive about Visual Format Language
  • Don't apply transform to views with top/left/bottom/right constraints
  • Don't change constraints inLayoutSubviews
  • NSLayoutConstraint has only one* mutable property - constant.
  • Activate/deactivate constraints instead of adding/removing
  • Minimise amount of constraints with outlets
  • Beware of what you animating

NSLayoutAnchor/UILayoutGuide available since iOS 9

Working with Autolayout

in a code

Animation

// Ensures that all pending layout operations have been completed
[containerView layoutIfNeeded]; 
[UIView animateWithDuration:1.0 animations:^{
     // Make all constraint changes here
     // Forces the layout of the subtree animation block and captures all of the frame changes
     [containerView layoutIfNeeded]; 
}];

Opting out from Autolayout

override func layoutSubviews() {
    mySubview.frame  = CGRectMake(...)
}

Working with Autolayout

in a code

- (void)updateConstraints {
    [self removeConstraints:self.imageViewConstraints];
    self.imageViewConstraints = [self createConstraints];
    [self addConstraints:self.imageViewConstraints];
    [super updateConstraints];
}

Updating/Adding constraints

Baseline alignment

- (UIView *)viewForBaselineLayout
{
    // create the view if not exists, start with rect zero
    if (!self.baselineView) {
        ...
    }
    int integerBaseline = ...
    // update the frame of the view
    self.baselineView.frame = CGRectMake(0, 0, self.bounds.size.width, (float)integerBaseline);
    return self.baselineView;
}

Working with Autolayout

in a code

activateConstraints: instead of addConstraints:

translatesAutoresizingMaskIntoConstraints

  • YES for any view you programmatically create.
  • NO If you add views in Interface Builder
  • Automatically adds the constraints to the correct view.

systemLayoutSizeFittingSize:

  • CGSize UILayoutFittingCompressedSize
  • CGSize UILayoutFittingExpandedSize
  • UILayoutPriorityFittingSizeLevel = 50

Working with Autolayout

in a code

Alignment rects

  • - alignmentRectForFrame:
  • - frameForAlignmentRect:
  • - alignmentRectInsets

Debugging Autolayout

Use identifiers

  • Constraints identifiers
  • Views identifiers
  • UILayoutGuides identifiers

Debugging methods

- constraintsAffectingLayoutForAxis:

- hasAmbiguousLayout

- exerciseAmbiguityInLayout

 

*<UIWindow:0x7fe781d25a80> - AMBIGUOUS LAYOUT
|   *<UIView:0x7fe781d29db0>
|   |   *<UIButton:0x7fe781d2a170>
|   |   |   <UIButtonLabel:0x7fe781c107d0>
|   |   *<UISlider:0x7fe781d2b9b0>
|   |   |   <UIView:0x7fe781d37fe0>
|   |   |   |   <UIImageView:0x7fe781d3a780>
|   |   |   <UIImageView:0x7fe781d3ac50>
|   |   |   <UIImageView:0x7fe781d3e590>
|   |   |   |   <UIImageView:0x7fe781d39a00>
|   |   *<_UILayoutGuide:0x7fe781d2c4b0> - AMBIGUOUS LAYOUT
|   |   *<_UILayoutGuide:0x7fe781d2cda0> - AMBIGUOUS LAYOUT
expr ((UIView *)0x7731880).backgroundColor = [UIColor purpleColor]
po [[UIWindow keyWindow] _autolayoutTrace];

Storyboards

  • Merges  
  • No DI via initialiser
  • Segues (Unwind segues)
  • Space to grow your screen naturally
  • Refactoring with Storyboard References

DEMO

Made with Slides.com