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
IB
By borlov
IB
- 1,398