preface

I haven’t written a UI in a long time. The latest version has a lot of UI written. Maybe because I started iOS development from Storyboard, I have a deep affection for Auto Layout. This time take advantage of the hand to write an article, I hope to be able to prepare to use and are using Auto Layout you help.

1. Auto Layout

What does Auto Layout do

The display area of UIView is determined by center and size. The four real numbers are centerX, centerY, width and height. Auto Layout is to solve the four unknowns through given constraints (that is, equations), essentially solving the problem of linear programming.

For example, IF I want to declare “this button is 3DP below that image, the two are the same size and center aligned”, this sentence can be converted to four constraints:


t o p b t n = b o t t o m i m g + 3 top_{btn} = bottom_{img} + 3

c e n t e r X b t n = c e n t e r X i m g centerX_{btn} = centerX_{img}

w i d t h b t n = w i d t h i m g width_{btn} = width_{img}

h e i g h t b t n = h e i g h t i m g height_{btn} = height_{img}

If the display area of the image is known, then it is obvious that all the layout information of the BTN can also be figured out.

As we all know, four independent equations are required to solve four unknowns, that is, four constraints are generally required to uniquely fix a view. There are exceptions, of course, and sometimes you just want to fix the top, left, and right of a textView so that it expands down with user input. Or you can just specify where the center of a label is and it will internally calculate its own size. Fewer constraints would be enough.

When to use Auto Layout? It is said to have poor performance

Unfortunately, Auto Layout does not perform very well. The algorithm it uses is Cassowary. Many articles on the Internet have been tested, here is a random post. This article deeply explores the reasons for the poor performance of Auto Layout: In addition to the time consuming of the linear equation itself, Apple’s own implementation has bigger problems. Apple has done a wave of optimizations in iOS12 that should fix this problem. Therefore, iOS12 and up using Auto Layout will not have much performance difference compared to manual Layout.

But don’t worry too much. In our experience, using Auto Layout won’t have a substantial performance impact on your App, regardless of whether it’s above iOS12, except for highly performance sensitive and frequently rearranged pages such as feed streams.

On the other hand, Auto Layout has its advantages: better readability and maintainability. Imagine that you need to add a badge icon to a complex layout of user business cards. There is a bunch of inexplicable logic in the existing code to control whether the love icon on the left is hidden, and whether the VIP icon is hidden… You don’t have time to reconstruct this mess. Now to insert this badge, you have to decide in turn: what is my frame without the thing in front hiding it? What is hidden? This leads to a bunch of if-else, which makes your code even more confusing.

Imagine if you wanted to adapt an iPad to an existing page… It’s the ultimate torture. However, if the page is originally written in Auto Layout, these problems can be greatly improved. If you are used to using Auto Layout, you will know this.

Therefore, in an environment of excess performance, these minor performance impacts are completely expendable compared to the maintainability and readability of the code.

Personally, mobile Q supports minimum iOS9, and I use Auto Layout whenever possible except for feed pages, unless I feel that using Auto Layout makes the code less readable and maintainable. For example, product logic requires that the size of a view have two possible values. Switch between these two sizes according to user operations. In order to update the constraint in other methods, I had to save it as a member variable, making my class look worse. You might as well set the size manually.

Where should I put my Layout code?

In general, just write the view where you create it. Let’s say viewDidLoad. However, if you really want to be more efficient and your constraints update frequently, you can write all the constraints that need to be updated in the same method that you define. Since the system is batch update-friendly, this can improve some efficiency. See this article for more details.

The latter, of course, sacrifices readability, which I don’t recommend. If your pages are so performance-sensitive that using Auto Layout affects their fluency, you should ditch Auto Layout and look for an asynchronous Layout solution.

2, Constraints,

In this section, we’ll start to discuss how to use Auto Layout in your code.

The statement constraints

Previously we verbally used the description “this button is 3dp below that image, the same size and center aligned”, now we need to declare it in the code. For example, the constraint “button is 3dp below that image” declares it in the following ways:

  • The most primitive method to useNSLayoutConstraintClass interface, the code looks like this:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:imageView attribute:NSLayoutAttributeTrailing multiplier:1 constant:3];This socket, as you can see, is smelly and long, and now you can assume that dogs don’t even use it.
  • Apple designed oneVFL(Visual Format Language), a string describing the layout, for example:@"V:[imageView]-3-[button]". VFL was designed to simplify the complex syntax of NSLayoutConstraint, but its rules seem more complex, and pure strings are more error-prone. No one would do that now, except for a few stunts.
  • Apple provides NSLayoutAnchor in iOS9, with syntax like this:[button.leadingAnchor constraintEqualToAnchor:imageView.trailingAnchor constant:3]It’s a little wordy, I have to say, but it’s acceptable. I recommend using this method.
  • Some third-party libraries encapsulate the interfaces of the system (said Massonry). Although the interface is more elegant, it is still a third-party library. This part has nothing to do with Auto Layout itself, so it is just mentioned, not introduced. You can choose what you are interested in.
  • Of course, there’s also Interface Builder that works perfectly with Auto Layout itself. But this thing really can’t be coordinated by many people, so it has to be taken for granted.

In summary, I’m going to add constraints via NSLayoutAnchor.

Using NSLayoutAnchor

Apple provides the following Anchors for UIView:

  • X axis direction: leading, trailing, left, right, centerX
  • Y axis: top, bottom, centerY, firstBaseline, lastBaseline
  • Other: width, height

To introduce these anchors, look at this line of code:

[button.leftAnchor constraintEqualToAnchor:imageView.rightAnchor constant:20]

The Auto Layout interface is self-explanatory enough that you should be able to guess which equation it represents, namely: Leftbutton =rightimageView+ 20Left_ {button} = right_{imageView} + 20LeftButton =rightimageView+20.

But in practical application, leftAnchor and rightAnchor are generally replaced by leadingAnchor and trailingAnchor. The difference between them lies in that in some areas that like to read from right to left, the leadingAnchor actually represents the right while the trailingAnchor represents the left. In our country, which reads from left to right, it can be considered leading==left, trailing==right. Instead of using left and right directly, use leading and trailing to avoid the hassle of matching. This is one of the advantages of Auto Layout.

In addition, there are two non-intuitive Anchor names mentioned above: firstBaseline and lastBaseline, which represent the baseline corresponding to the first and last lines of the multi-line text component. But they’re really rare, so if you don’t understand them, you can ignore them for a while.

Let’s go back to that line of code. It just creates the Constraint, but it doesn’t actually take effect. The return value of this line of code is an instance of NSLayoutConstraint, and you need to set its active field to YES for it to work.

Be careful, however, that you need to do at least two things before creating this constraint: first, place the Button and imageView into a view tree. In other words, it’s all going to addSubview. Next, execute this code:

button.translatesAutoresizingMaskIntoConstraints = NO;

TranslatesAutoresizingMaskIntoConstraints is compatible to power past Auto Resizing logic of regret. It translates the relationships described in Auto Resizing into constraints in Auto Layout. If you want to add constraints yourself, you must set this field to NO.

Now we should be able to write “this button is 3dp below that image, the same size, and center aligned” code more completely:

// Make sure they are in the same view tree (parent, sibling, or more distant, as long as they have a common ancestor), otherwise Crash will occur
[contentView addSubview:imageView];
[contentView addSubview:button];

button.translatesAutoresizingMaskIntoConstraints = NO;

// The top of the button is 3dp from the bottom of the imageView
[button.topAnchor constraintEqualToAnchor:imageView.bottomAnchor constant:3].active = YES;

// Same width and height, horizontally aligned in the center
[button.widthAnchor constraintEqualToAnchor:imageView.widthAnchor].active = YES;
[button.heightAnchor constraintEqualToAnchor:imageView.heightAnchor].active = YES;
[button.centerXAnchor constraintEqualToAnchor:imageView.centerXAnchor].active = YES;
Copy the code

Above statement constraint method is [Anchor1 constraintEqualToAnchor: Anchor2]. This interface also has two optional parameters, constant and Multiplier, the latter of which applies to Anchor2. That is:

[Anchor1 constraintEqualToAnchor:Anchor2 mutiplier:m constant:c]

Is equivalent to:


A n c h o r 1 = m A n c h o r 2 + c Anchor1 = m * Anchor2 + c

If you want to constrain the View with only one constant, you can do so without involving other Anchors. Statement width the width of the button to 40, for example, only need to use this interface: [button. WidthAnchor constraintEqualToConstant: 40].

In addition, NSLayoutAnchor has two other ways of declaring constraints by replacing Equal in the interface above with GreaterThanOrEqualTo or LessThanOrEqualTo. As the name suggests, this is a “soft” constraint that gives your view some flexibility. For example, if your textView grows with user input, but you don’t want it to grow beyond 200dp, you can declare it as follows:

[textView.heightAnchor constraintLessThanOrEqualToConstant:200].active = YES;

As long as you keep in mind that every constraint you write is an equation (or inequality), you won’t need to memorize it at all, and you’ll soon be able to use these interfaces easily.

The Constraint of the Priority

Imagine a scenario: there is a checkbox that the user can drag to zoom in and out. The checkbox has an imageView to display the background image, which is the same size as the checkbox and is scaled as the user moves it. However, the size of the background image is limited, and the height cannot exceed 320dp. That is, the background image should be the same size as the user’s zoom box when it is within 320dp; Once it passes 320DP, it stops growing.

In order to restrain its height, you are likely to make the following statement (which expandingViewexpandingViewexpandingView selected frame, says bgViewbgViewbgView said background. This is not a good name, it’s done just to look succinct) :

HeightbgView =heightexpandingView①height_{bgView} = Height_ {expandingView} \quad ①heightbgView=heightexpandingView① HeightbgView ≤320②height_{bgView} \le 320 \quad ②heightbgView≤320②

Obviously, the two constraints conflict when heightexpandingView> 320Height_ {expandingView} > 320heightexpandingView>320. In fact, I should want the constraint ② to have a higher priority, so that in case of a conflict, Auto Layout can make the appropriate trade-offs, using ② and ignoring ①.

Remember [bgView heightAnchor constraintLessThanOrEqualToConstant: 320] returns a NSLayoutConstraint instance, it has an attribute priority, is doing this. This is a float value ranging from 0 to 1000. A larger number indicates a higher priority. Apple already predefined some values, such as UILayoutPriorityRequired is 1000. By default, all constraints you add are UILayoutPriorityRequired.

Therefore, you can reduce the priority of constraint 1 by:

NSLayoutConstraint *equalHeightConstraint = [bgView.heightAnchor constraintEqualToAnchor:expandingView.heightAnchor];
equalHeightConstraint.priority = UILayoutPriorityRequired - 1;
equalHeightConstraint.active = YES;
Copy the code

In more complex, dynamic layouts, constraints can often help clarify your thinking. This saves a lot of code compared to manual layout and the hassle of calling back to update the frame. This also contributes to clean and cohesive code.

3, StackView

In iOS 9, in addition to NSLayoutAnchor, iOS also brought another little gift: UIStackView.

Tabular layouts are probably the most common. It is really stupid to calculate their frames one by one. When I use tableView and collectionView, I feel like opening the bottle cap of a forklift truck, which is a little too heavy. UIStackView is a good example of a lightweight tabular layout component. It uses Auto Layout entirely internally. According to some of the older reviews, it didn’t perform very well. But that was a long time ago. The same as Auto Layout itself, performance problems must occur in the scenario of multi-layer nesting and frequent Layout, and there is no problem in daily use in general scenarios. In my time with it, I have never encountered a performance impact. We suggest that you rest assured use.

UIStackView supports horizontal and vertical list layout, as determined by the Axis attribute. Convenience, in the introduction below, I mostly use horizontal layout (UILayoutConstraintAxisHorizontal) as an example.

Alignment

I have now created a horizontal stackView for a tabular layout, but the heights of the views in the list are quite different. Like a bunch of mummies lying in a row, what’s the best way to align them? You might want to line them up on the top of your head, or have the soles of your feet on the same level, or even stretch and press them to make sure they’re all exactly the same height so they’re perfectly aligned. All of these will do. The alignment field does just that.

The alignment attribute (UIStackViewAlignment) has the following optional values:

  • B: top, bottom, firstBaseline, lastBaseline
  • Vertical direction: leading, trailing
  • Universal: center, fill

Just looking at their names should be enough to know the alignment. But TO be on the safe side, I’ll post some examples below. These examples were taken after I actually used the Storyboard, where the green is the stackView area and the gray is the child view. Incidentally, stackView is a subclass of UIView, but it doesn’t actually render — that’s what it claims to be lighter than UIView — so setting backgroundColor to it doesn’t work. If you want to color the background green as shown in the image, either put a View underneath it or use its Layer backgroundColor.

UIStackViewAlignmentTop

UIStackViewAlignmentCenter

UIStackViewAlignmentBottom

UIStackViewAlignmentFill

UIStackViewAlignmentFirstBaseline

UIStackViewAlignmentLastBaseline

Where the fill mode stretches the child View. FirstBaseline fixes the highest view at the bottom, then aligns the text component’s first baseline with its top, and finally aligns the top of other views as well. LastBaseline is the opposite.

Distribution

Above we solved the problem of the height of the subview, but I left the width out. In the example diagram above, although each child View has a different height, they all have the same width. This is not because I added any additional constraints; I simply set stackView’s distribution property value to fillEqually.

As the name suggests, this value causes stackView to scale the width of all the child views to make sure they are the same width and fill the stackView. Distribution is the basis for stackView decisions on how to arrange child views in an axial manner. All of its values are as follows (if you don’t understand them at first, don’t worry about them in the next section) :

  • Fill: The default behavior, which ensures that the child view fills the stackView axially (in our case, horizontally).
  • FillProportionally: it will try to keep each subview as long as it is filledintrictionContentSize. This is the size calculated by a system, for example, yesUILabelEven if you don’t specify itboundsIt can also calculate the correct size.
  • FillEqually: As mentioned earlier, each child view is guaranteed to be the same width or height, depending on what you wantaxisThe value of the
  • EuqalSpacing: As the name implies, ensures that each child view is equally spaced.
  • Equalpress: equalSpacing: This is similar to equalSpacing, except the spacing of each child view is the sameThe midlineThe distance between.

Making sample diagrams of these properties is a bit of a hassle, so I’ve taken them straight from this article. This article introduces UIStackView in more detail, and is worth a look if you have more time:

Priority

On the face, in the introduction of UIStackViewDistributionFillProportionally I used the “try to keep this kind of ambiguous words,” this is because the stackView can’t guarantee completely carried out in accordance with the intrictionContentSize layout.

Not only in the fillProportionally, each stackView distribution ensures that the child views fill the stackView axially. It is inevitable that the child view will not fill in the width/height or overflow. StackView has to stretch or compress these child views in the principle of more give, less make up.

If that happens, which child view should I attack? According to tradition, persimmon should be picked soft. UIView has two attributes: contentHuggingPriority and contentCompressionResistancePriority. As the name suggests, the former indicates how tightly the view is held: the tighter it is, the harder it is to pull it apart; The latter represents the view’s determination to resist compression: the higher the value, the harder it is to compress. The two prorities are independent of each other on the X and Y axes. They have the same range of values as the priority of the NSLayoutConstraint mentioned earlier. You can set them through UIVew’s two interfaces:

- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));

- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis API_AVAILABLE(ios(6.0));
Copy the code

In many cases, these two priorities are the key to solving UIStackView typography problems.

Use StackView for typesetting

Above, we have filled in the three main stackView properties: Axis, Distribution, and alignment. You typically specify the values of these three properties after initializing stackView. We are now ready to traverse to the child the view in the stackView, this is very simple, just use it addArrangedSubview: method or insertArrangedSubview: atIndex: method.

StackView rules will only management through addArrangedSubview: and insertArrangedSubview: atIndex: add a child of the view. Both methods automatically call addSubview: internally, so most of the time you don’t need to manually call the addSubview: method of UIStackView.

On the other hand, the removeArrangedSubview: method of a UIStackView does not automatically call the removeFromSuperview method of the child view. So you don’t normally use this method. To remove a subview, simply call theSubview removeFromSuperview.

You can specify the spacing between child views through the Spacing attribute of the stackView. This property has a default value, which Apple explains as follows:

System spacing between views depends on the views involved, and may vary across the stack view.

Such instructions are really above my head. I’ll steal Mattt’s meme: Don’t use this default unless you have a relative who works in Apple’s UIKit department.

In addition to the spacing, UIStackView has an interface setCustomSpacing: afterView: spacing between son can also customize the view, but the interface is iOS 11.

An example using StackView

Next I use a small example to show the charm of UIStackView:

Suppose you want to design a capsule view that has an icon on the left, a button on the right, and a text box in the middle. Depending on the length of the text, the entire view should be scaled accordingly. As shown in the figure:

Null input. I have to admit, it’s not particularly pretty

When I put something in

When a large amount of content is entered so that the width overflows. The black on each side indicates the phone’s border

Imagine what you could do with traditional typography. I’m afraid you’ll have to listen for the contents of the middle textField to change, and then trigger a method, maybe called updateWidth, that calculates and updates the width of the entire view. You also have to be careful to determine if the width exceeds the screen width and thus truncate the input.

In fact, this is a relatively simple layout. If the middle is not an input field but a custom complex business view, you might even want to listen for content changes in that view, passed externally through a delegate to update the width. This is very bad for the cohesion of the code.

With StackView/Auto Layout, however, you can do all this by adding a few extra lines of code when you create the view. The specific steps are as follows:

  • Create stackView and set its distribution tofillProportionallyAnd pass the three child ViewsaddArrangedSubview:Method to add.
  • Constraint stackView width not greater than (LessOrEqualThan) Screen width.
  • For the final touch, lower the textFieldcontentCompressionReistancePriorityTo ensure that in the event of a width overflow, the width of the view is prioritized over the width of the left and right views.

With these simple processes, we can easily accomplish things that require a lot of work in traditional typography. This not only saves you a lot of work, but also improves the readability and cohesiveness of your code.

4. Frequently asked Questions

Why did my Constraint not take effect?

Well, the most likely is that you forgot to set the translatesAutoresizingMaskIntoConstraints to NO. It happens all the time. Get used to it.

If this is not the case, you may want to use Xcode’s Debug View Hierarchy feature to View all constraints of the View in question in the Debug Navigator on the left to see if there are any overadditions or conflicts. There is usually a purple exclamation mark to alert you if there is a conflict. In this case, you can remove unnecessary constraints. If you can’t remove it, try sorting it out with Priority.

How do I animate with Auto Layout?

I don’t come across this scene very often. But it certainly can be done. For example, if you want to dynamically scale a view to 0, try the following code:

// The viewHeightConstraint here is an instance of NSLayoutConstraint you have generated by Viet.heightanchor
viewHeightConstraint.constant = 0;
[UIView animateWithDuration:0.25 animations:^{
    [view layoutIfNeeded];
}];
Copy the code

Why is StackView fucking scaling my view?

In this case, you first need to make sure that the distribution and alignment choices are correct, and that the constraints on the child view are exactly what you expect. If your alignment is not a fill, then you may need to add a radial constraint, or give an initial value for the dimensions in that direction. Otherwise the dimension for this direction will be set to 0 by default, and it will not be displayed.

On the axis, stackView ensures that the child views fill up no matter which distribution you use. So, if your stackView is longer than it needs to be, you can either scale it to the right size or add a placeholder view with a lower contentHuggingPriority and let the stackView stretch — if you’ve heard of UILayoutGuide, You’ll find it sounds a lot like what it should do. Unfortunately, UILayoutGuide doesn’t work well with UIStackView, so you’re stuck with the primitive placeholder view.

Finally, if the constraint is not a problem, then adjusting priority must solve the problem. The constraint priority added to stackView is 1000, so be careful that your constraints don’t conflict with it. In addition, adjust the child view contentHuggingPriority and contentCompressionResistancePriority to ensure that they are expected to scale.

conclusion

I mentioned “cohesion” and “readability” several times earlier, which I think are Auto Layout’s greatest strengths. While debugging layout clutter can be a bit of a pain in the ass in the beginning, you’ll often be pleasantly surprised when you get used to it. So I recommend you try Auto Layout and UIStackView. I hope I can help you.