The original link

I will read the source code for navigation to learn about its design. However, after reading part of it, I found that my understanding of iOS auto layout was not systematic enough and not deep enough. So get ready to learn the basics of iOS auto layout.

Here are some of the things I’ve done with the iOS layout system, with auto layout being the main focus.

An overview of the

Apple introduced Absolute Layout with the iPhone 4 and Auto Layout with iOS 6 as the number of iOS devices grew. In the process of gradually improving the automatic layout, Apple also launched such technologies as Size Class, Stack View, UILayoutGuide, etc., but their essence is based on automatic layout.

source

1997 Alan Boring, Kim Marriott, Peter Stuckey et al., in their published paper Solving Linear Arithmetic Constraints for User Interface Applications, identified Cassowary to solve the layout problem Constraints – Solving algorithm is implemented.

In 2011, Apple applied Cassowary’s algorithm to its Layout engine, Auto Layout.

Cassorwary

Cassowary can effectively parse linear equality systems and linear inequality systems to represent equality and inequality relationships in user interfaces. Based on this, Cassowary has developed a rule system that describes the relationships between views through constraints. Constraints are rules that represent the position of one view relative to another.

Due to the advanced nature of Cassowary’s algorithm, many programming languages have implemented corresponding libraries, such as JavaScript,.net, Java, SmallTalk, C++.

The constraint

Cassowary’s core is to describe the relationships between views based on constraints. The constraint is essentially an equation:

Attribute1 = Multiplier × item2. Attribute2 + constantCopy the code

Let’s introduce the constraint equation with a simple constraint.

This constraint means that the left edge of the red view is 8 pixels to the right of the blue view. Note that = doesn’t mean assignment, it means equality.

In an automatic layout system, constraints can define not only the relationship between two views, but also the relationship between two different properties of a single view, such as setting a ratio between the height and width of the view. In general, a view requires four constraints to determine its size and location.

Constraint rules

The constraint equation above mainly describes the relationship between two view attributes. So, let’s take a look at what properties and relationships iOS defines.

attribute

Apple uses an enumerated value of type NSLayoutAttribute to represent layout attributes, which mainly contain the following attributes:

typedef NS_ENUM(NSInteger.NSLayoutAttribute) {
    // View position
    NSLayoutAttributeLeft = 1.NSLayoutAttributeRight.NSLayoutAttributeTop.NSLayoutAttributeBottom.// Before and after the view
    NSLayoutAttributeLeading.NSLayoutAttributeTrailing.// View width and height
    NSLayoutAttributeWidth.NSLayoutAttributeHeight.// View center
    NSLayoutAttributeCenterX.NSLayoutAttributeCenterY.// View baseline
    NSLayoutAttributeLastBaseline.NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),
    
    NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
    
    // placeholder, which can be used when an attribute is not used in a relationship with another constraint
    NSLayoutAttributeNotAnAttribute = 0
};
Copy the code

It is worth noting that NSLayoutAttribute similar NSLayoutAttributeLeft and NSLayoutAttributeLeftMargin enumeration. The difference between the two is:

  • NSLayoutAttributeLeftRepresents the leftmost part of the view;
  • NSLayoutAttributeLeftMarginRepresents the left side of the view, and how far from the left side is the margin of the viewlayoutMarginsThe relevant.

We’ll talk about layoutMargins later on.

Relationship between

Apple uses an enumerated value of type NSLayoutRelation to represent attribute relationships, which mainly include the following relationships:

typedef NS_ENUM(NSInteger.NSLayoutRelation) {
    NSLayoutRelationLessThanOrEqual = - 1.NSLayoutRelationEqual = 0.NSLayoutRelationGreaterThanOrEqual = 1};Copy the code

The constraint level

A constraint describes the relationship between two views, but only if both views belong to the same view hierarchy. There are two types of hierarchy:

  1. A view is a view of another view
  2. Both views have a not under one windownilThe common ancestor view of.

Constraint priority

Constraints have priority. When a layout engine evaluates a layout, it evaluates it one by one in order of priority from highest to lowest. If it is found that an optional constraint cannot be satisfied, the constraint is skipped and the next constraint is evaluated. Sometimes a constraint can affect the layout even if it doesn’t fit perfectly.

Apple defines four priority enumerated values by default. In addition, Apple allows you to create other priorities, but they must be in the range of 1 to 1000.

static const UILayoutPriority UILayoutPriorityRequired = 1000; 
static const UILayoutPriority UILayoutPriorityDefaultHigh = 750; 
static const UILayoutPriority UILayoutPriorityDefaultLow = 250; 
static const UILayoutPriority UILayoutPriorityFittingSizeLevel = 50; 
Copy the code

Constraints to create

For constraint creation, Apple provides Interface Builds that allow constraints to be created in a non-programmatic way. But in large projects, we still create constraints primarily programmatically.

There are three main ways to create constraints programmatically:

  • Constraint constructor (NSLayoutConstraint)
  • Layout Anchors
  • Visual Format Language (VFL)

Let’s introduce them one by one.

NSLayoutConstraint

Apple uses the NSLayoutConstraint type to represent constraints. The NSLayoutConstraint class provides a constructor to create the constraint directly. The parameters of the constructor correspond to the terms of the constraint equation.

+ (instancetype)constraintWithItem:(id)view1 
                         attribute:(NSLayoutAttribute)attr1 
                         relatedBy:(NSLayoutRelation)relation 
                            toItem:(id)view2 
                         attribute:(NSLayoutAttribute)attr2 
                        multiplier:(CGFloat)multiplier 
                          constant:(CGFloat)c;
Copy the code

Layout Anchors

Apple uses the NSLayoutAnchor type to represent layout anchors. Before introducing NSLayoutAnchor, we need to introduce some other concepts such as UILayoutGuide (used when using NSLayoutAnchor).

UILayoutGuide

UILayoutGuide is a virtual rectangular area that can be thought of as a transparent UIView, but it is not added to the view hierarchy, nor does it block message calls, and is used to interact with Auto Layout. For example, three UIViews are in a row with the same spacing between them. The interval can be replaced with UILayoutGuide.

Now that we know about UILayoutGuide, we need to know about two UIView properties (instances of the UILayoutGuide type) :

  • layoutMarginsGuide: first appeared on iOS 9
  • safeAreaLayoutGuide: first appeared on iOS 11

layoutMarginsGuide

UIView has a property of type UIEdgeInsets, layoutMargins, which represents the margins between the content of a view and its four margins, as shown below.

The layoutMarginsGuide property of UIView is just another form of layoutMargins that can be used to create layout constraints. LayoutMarginsGuide is a read-only property.

safeAreaLayoutGuide

With iOS 11, Apple introduced the concept of Safe Area. Since the iPhone X on iOS 11 removed the Home button, it was necessary to reserve some space for Navigation Bar, Status Bar and Tab Bar. The safeAreaLayoutGuide property comes with Safe Area.

The safeAreaLayoutGuide property, like the layoutMarginsGuide property, is read-only because they all have a virtual region set by default, and we can set constraints directly based on that region.

NSLayoutAnchor

With a brief introduction to UILayoutGuide, let’s take a look at the members it contains. As you can see, UILayoutGuide internally defines a number of members of the NSLayoutAnchor type.

In fact, the NSLayoutAnchor class can use a set of apis to create constraint objects of type NSLayoutConstraint to set layout constraints without directly dealing with the NSLayoutConstraint object.

Normally, we don’t use NSLayoutAnchor directly, but use three subclasses of it, as follows:

  • NSLayoutXAxisAnchor: X-axis anchor point used to create horizontal constraints
  • NSLayoutYAxisAnchor: anchor point in the Y direction, used to create vertical constraints
  • NSLayoutDimension: dimensioned anchor point used to create dimensioned constraints

The many anchor properties of UILayoutGuide can be grouped into one of the three sub-categories.

Anchor point property in the X-axis direction Anchor point properties in the Y direction Size dependent anchor point properties
centerXAnchor centerYAnchor widthAnchor
leftAnchor topAnchor heightAnchor
rightAnchor bottomAnchor
leadingAnchor firstBaselineAnchor
trailingAnchor lasBaselineAnchor

Note: The effects of leadingAnchor and leftAnchor, trailingAnchor and rightAnchor are the same in most cases, but there are essential differences: LeadingAnchor represents the border anchor at the front of the view. In countries such as English that read from left to right, leading means left, but in countries such as Arabic that read from right to left, leading means right.

One view’s multiple anchor attributes (X, Y, dimensions) can interact with the corresponding position and dimension anchors of another view to determine the position and dimension of the view. Anchor relationships between views can create constraints through API calls. The premise is that only anchor properties of the same subclass can interact with each other. That is:

  • The X-axis anchor attribute can only interact with the X-axis anchor attribute.
  • Anchor attributes in the Y-axis direction can only interact with anchor attributes in the Y-axis direction.
  • Dimensional anchor properties can only interact with dimensional anchor properties.

Let’s compare two ways to create a constraint:

  1. useNSLayoutConstraintCreate constraints directly
  2. useNSLayoutAnchorIndirect create constraint
// 1.Creating constraints using NSLayoutConstraint
NSLayoutConstraint(item: subview,
                   attribute: .leading,
                   relatedBy: .equal,
                   toItem: view,
                   attribute: .leadingMargin,
                   multiplier: 1.0,
                   constant: 0.0).isActive = true

NSLayoutConstraint(item: subview,
                   attribute: .trailing,
                   relatedBy: .equal,
                   toItem: view,
                   attribute: .trailingMargin,
                   multiplier: 1.0,
                   constant: 0.0).isActive = true

// 2. Creating the same constraints using Layout Anchors
let margins = view.layoutMarginsGuide

subview.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
subview.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
Copy the code

Some of the methods for creating constraints indirectly for the NSLayoutAnchor are shown below.

// Common API inherited from NSLayoutAnchor
func constraint(equalTo anchor: NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint
func constraint(equalTo anchor: NSLayoutAnchor<AnchorType>, constant c: CGFloat) -> NSLayoutConstraint

/ / NSLayoutXAxisAnchor API
func constraint(equalToSystemSpacingAfter anchor: NSLayoutXAxisAnchor, multiplier: CGFloat) -> NSLayoutConstraint
func anchorWithOffset(to otherAnchor: NSLayoutXAxisAnchor) -> NSLayoutDimension

/ / NSLayoutYAxisAnchor API
func constraint(equalToSystemSpacingBelow anchor: NSLayoutYAxisAnchor, multiplier: CGFloat) -> NSLayoutConstraint
func anchorWithOffset(to otherAnchor: NSLayoutYAxisAnchor) -> NSLayoutDimension

/ / NSLayoutDimension API
func constraint(equalTo anchor: NSLayoutDimension, multiplier m: CGFloat) -> NSLayoutConstraint
func constraint(equalTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint
func constraint(equalToConstant c: CGFloat) -> NSLayoutConstraint
Copy the code

VFL

VFL (Visual Format Language) is a Domain-specific Language (DSL) introduced by Apple to simplify Auto Layout coding.

grammar

instructions The sample
The standard interval [button]-[textField]
The width of the constraint [button(>=50)]
Relationship to the superview |-50-[purpleBox]-50-|
Vertical layout V:[topField]-10-[bottomField]
Flush Views [maroonView][blueView]
priority [button(100@20)]
Width is equal [button(==button2)]
Multiple Predicates [flexibleButton(>=70,<=100)]

Note the following when creating a VFL statement description:

  • H:V:You can only use one at a time
  • The view variable name appears in square brackets, as in:[blueView]
  • Order of statements: top to bottom, left to right
  • View intervals appear as numeric constants, such as:- 10 -
  • |Represents the superview

The NSLayoutConstraint class provides apis that allow constraints to be created through VFL statements.

+ (NSArray<NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format 
                                                       options:(NSLayoutFormatOptions)opts 
                                                       metrics:(NSDictionary<NSString *,id> *)metrics 
                                                         views:(NSDictionary<NSString *,id> *)views;
Copy the code

Format indicates the VFL statement. Options indicates the constraint type. Metrics indicates the specific values used in the VFL statement. Views Indicates the control used in the VFL statement.

Let’s look at an example of creating constraints using VFL.

NSNumber *left = @50;
NSNumber *top = @50;
NSNumber *width = @100;
NSNumber *height = @100;
NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2);
NSDictionary *metrics = NSDictionaryOfVariableBindings(left, top, width, height);
[view1 addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];
Copy the code

Layout factors

The construction of a Layout is mainly done by the Layout Engine. Without a doubt, views are the object of building a layout. As the core of automatic layout, constraint is an important basis for constructing layout. In addition, the layout engine takes these factors into account when building a layout:

  • Constraint Priorities
  • Content Priorities
  • Intrinsic Content Size
  • Sizing Constraints
  • Horizontal Alignment
  • Vertical Alignment
  • Baseline Alignment
  • Alignment Rect

The size constraint

In fact, the constraints created in constraint creation above already contain size constraints. Here again, size constraints are addressed primarily for the self-sizing view.

For example, we can automatically calculate the Cell height of a TableView through automatic layout. However, this feature is not enabled by default.

By default, TabelView Cell height by agreement statement tableView: heightForRowAtIndexPath: method to determine. In addition, we can enable self-sizing by assigning two properties of the TabeView, as shown below:

tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
Copy the code

Next, we need to lay it out in the contentView of the TableView Cell. In order for the layout engine to automatically calculate the height of the Cell, we must define a good set of vertical constraints on the child views of the contentView, especially the height constraints. In the layout engine’s calculation of height, it uses dimension constraints first, and intrinsic content dimensions second.

Inherent content size & content priority

Some views in iOS have intrinsic content Size, which is the size occupied by the view’s content and margins. For example, the inherent content size of UIButton is equal to the size of the Title plus the content margin.

Views with inherent content dimensions have the following:

View Intrinsic Content Size
Sliders Defines the width, height, or both — depending on the slider’s type (OS X).
Labels, buttons, switches, and text fields Defines both the height and the width.
Text views and image views Intrinsic content size can vary.

The size of the inherent content size is influenced by many factors. In the case of a UITextView, the inherent content size depends on the content, whether scrolling is enabled, and other constraints that apply to the UITextView. If scrollable, there is no inherent content size, if not, it depends on the size of all the text.

The size of the inherent content size is also influenced by the content priority, which has the following two aspects:

  • Content Hugging Priority
  • Content Compression Resistance Priority

Content Hugging Priority: Specifies the stretch Priority of a view. The higher the value, the higher the Priority, and the harder it seems to stretch.

Content Compressing Priority: indicates the compression Priority of a view. The higher the value, the higher the Priority and the harder it is to compress.

By default, the value of Content Select Priority is 250 and Content Compression Resistance Priority is 750. Therefore, it is easier to stretch a view than to compress it.

Intrinsic Content Size relationship with Fitting Size

The Intrinsic Content Size is the input to the layout engine from which constraints can be generated, and ultimately the layout; Fitting Size, on the other hand, is the output of the layout engine and is the result of the layout generated based on constraints.

alignment

There are three types of alignment:

  • Horizontal alignment
  • The vertical alignment
  • Baseline alignment

For the first two, through the previous description we also have some understanding. Horizontal alignment, used to create constraints on the X-axis; Vertical alignment, used to create constraints on the Y axis.

Baseline alignment is a proprietary alignment for text. Baseline alignment includes firstBaseline and lastBaseline. As follows:

Align the rectangular

In automatic layout, we might think that the constraint uses a frame to determine the size and position of the view, but in fact it uses an alignment rect. In most cases, frame and alignment rect are equal, so this is not a bad idea.

So why use alignment Rect instead of Frame?

Sometimes, when creating complex views, we may add decorative elements such as shadows, corner markers, etc. In order to reduce the development cost, we will directly use the cut drawings given by the designer. As follows:

Where, (a) is the cut diagram given by the designer, and (c) is the frame of this diagram. Obviously, we do not want to include shadows and markers in the layout (the center and bottom and right edges of the view are offset), but only the central core, as shown in the box in Figure (b).

Alignment rectangles are used to deal with this. UIView provides methods to get an alignment rect from a frame and a frame from an alignment rect.

// The alignment rectangle for the specified frame.
- (CGRect)alignmentRectForFrame:(CGRect)frame;

// The frame for the specified alignment rectangle.
- (CGRect)frameForAlignmentRect:(CGRect)alignmentRect;
Copy the code

In addition, the system provides a simple method with UIEdgeInsets to specify the relationship between frame and alignment rect.

// The insets from the view’s frame that define its alignment rectangle.
- (UIEdgeInsets)alignmentRectInsets;
Copy the code

If you want an alignment rect with 10 more points than the bottom of the frame, you can write:

- (UIEdgeInsets)alignmentRectInsets {
    return UIEdgeInsetsMake(. 0.. 0.10.0.. 0);
}
Copy the code

Layout of the rendering

IOS layout rendering can be divided into three stages, as shown below:

  1. Constraints Update
  2. Layout Update
  3. Display Redraw

Each step is dependent on the previous one. Display redraw depends on layout updates, and layout updates depend on constraint updates.

Constraint updated

Constraint updates occur from the bottom up, from the child view to the parent view. We can call setNeedsUpdateConstraints update to trigger the constraints. Of course, we have a lot of respect for layout factors (constraints/content priority, constraints, inherent content size…) Any changes made will automatically trigger setNeedsUpdateConstraints method.

For custom views, we can rewrite updateConstraints during the constraint update phase to add the required local constraints to the view.

Layout update

Layout updates are top-down, from parent to child views. In fact, the layout update operation applies the results of the layout engine calculations to the view by setting frame (OS X) or Center and Bounds (iOS). We can trigger layout updates by calling setNeedsLayout. This does not apply the layout immediately, but rather delays processing. All layout requests will be merged into one layout operation. This Deferred processing process is called a Deferred Layout Pass.

We can call layoutIfNeeded (iOS) or layoutSubtreeIfNeeded (OS X) to force the system to update the layout of the view tree immediately. This is useful if our next steps depend on updating the frame in the rear view.

For custom views, we can override layoutSubviews (iOS) or Layout (OS X) during the layout update phase to gain ownership of control over layout changes.

According to redraw

Display redraw top-down (from parent view to child view). We can trigger the display redraw by calling setNeedsDisplay, which causes all calls to be merged together to delay the redraw.

For custom views, we can re-draw Rect: during the display redraw phase to gain ownership of the self-display process.

Matters needing attention

It is important to note that these three phases are not one-way. Constraint-based layout is an iterative process. A layout update can make changes to the constraints based on the previous layout, which again triggers a constraint update, followed by another layout update. This can be used to create advanced custom view layouts. But if every call to custom layoutSubviws resulted in another layout operation, we would be stuck in an infinite loop.

Automatic layout for different versions of iOS

iOS 6

  • Apple introduced auto layout with all the core features in this release.

iOS 7

  • NavigationBar, TabBar, ToolBartranslucentProperty defaults toYES. The height of the current ViewController is the height of the entire screen, which can be used in the layout to ensure it is not overwritten by these barstopLayoutGuidebottomLayoutGuideProperties.

iOS 8

  • The Self – Sizing Cells. Portal: www.appcoda.com/self-sizing…
  • UIViewControllerTwo new methods are added for processingUITraitEnvironmentThe agreement. Are there in UIKitUIScreen,UIViewController,UIPresentationControllerSupport the protocol. When the view traitCollection changes,UIViewControllerThis message can be captured for processing.
- (void)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
Copy the code
  • The Size of the Class.UIViewControllerA new set of protocol support is providedUIContentContainer.
- (void)systemLayoutFittingSizeDidChangeForChildContentContainer:(id )container NS_AVAILABLE_IOS(8_0);
- (CGSize)sizeForChildContentContainer:(id )container withParentContainerSize:(CGSize)parentSize NS_AVAILABLE_IOS(8_0);
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator NS_AVAILABLE_IOS(8_0);
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id )coordinator NS_AVAILABLE_IOS(8_0);
Copy the code
  • UIViewMargin added 3 new apisNSLayoutMarginsYou can define the distance between views. Valid only for automatic layouts, and the default is{8, 8, 8, 8}.NSLayoutAttributeThe enumeration values of the.
// UIView's 3 margin-related apis
@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0);
@property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0);
- (void)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0);

// The NSLayoutAttribute enumeration is updated
NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
Copy the code

iOS 9

  • UIStackView: provides simpler automatic layout methods, such as alignment fill, leading, Center, trailing. Distribution’s fill, fill equally, fill proportionally, equal spacing.
  • NSLayoutAnchor API
NSLayoutConstraint *constraint = [view1.leadingAnchor constraintEqualToAnchor:view2.topAnchor];
Copy the code

reference

  1. Solving Linear Arithmetic Constraints for User Interface Applications
  2. Visual Format Language
  3. Understanding Auto Layout
  4. NSLayoutAnchor basic knowledge
  5. Analyze Auto Layout and new features of iOS versions
  6. IOS automatic layout basics
  7. WWDC 2015 session 218 Mysteries of Auto Layout Part1
  8. WWDC 2015 session 219 Mysteries of Auto Layout Part2
  9. WWDC 2018 session 220 High Performance Auto Layout
  10. Advanced Auto Layout Toolbox
  11. WWDC 2015 – Mysteries Of AutoLayout (Mysteries Of AutoLayout)

Further reading

  1. IOS uses AutoLayout to automatically adjust view intervals
  2. Ios Auto Layout DemyStified
  3. Advanced automatic layout toolbox
  4. About UIView translatesAutoresizingMaskIntoConstraints properties
  5. Masonry
  6. IOS layout Rendering — UIView method call timing
  7. UIStackView basics

(after)