Thank you for your contribution from Cat friend Yin Jiaji. Jiaji’s interests lie in iOS/Hybrid App development. There is a link to his blog at the bottom of this article. Please also pay attention to the bottom of the article cat friends and owl public account, we will continue to provide quality content.

As an iOS developer, you all know that UIKit by default is MVC, Model, View, and Controller. With the popularity of App development in recent years, the technologies used in these three parts are becoming more and more mature. For example, models now have a lot of JSON-binding like Mantle, JSONModel; The control layer represented by Controller also has many ideological trends like MVVM, MV; , for View, now out of the UI control is countless, dazzling. Amid all that change, are there some things that remain the same? Yes, there are some core ideas that have not changed, such as Layout technology we are going to talk about today.

Layout is a topic that we often contact, but seldom pay attention to. Because it’s so easy to use, it’s all about changing the size, changing the position. I didn’t feel the need to do anything more.

Isn’t that setFrame?

It’s just setFrame, but don’t underestimate it. Look closely and you will see a whole world. Layout technology looks simple, but it is the basis of the entire GUI framework, the design of the upper technology, is to rely on it to achieve.

Code vs xib family

Long ago (how long ago? There are two ways to write a Layout for a view.

  1. Write in pure code, inherit UIView, UIButton, etc., implement custom View

  2. Nib, XIB, and later Storyboard were used to drag controls for visual editing

Which is better?

There is no fixed answer to this, either one is good or the other is bad, it depends on the situation, because the product business is different, the technology stack is different, and people still have technology preferences. So you can’t say the same thing. But will you use it? It’s obvious. Technology works best when it works with people who know it. So it’s not so much a good or bad thing as a style, and people who write in code find it easier to look at code in pure code, to inherit it, to combine it, to manage it. People who use XIBs find xiBS and storyboards more intuitive and easier to style. Whatever choice, to a developer, you are need to understand the pros and cons of both to make appropriate choices, on an iOS team, is the need of unified thought, to play the advantages of a way to set up rules to avoid the influence of faults, such as the use xib file, is not conducive to the merge, especially in the case of collaboration, some of the team at this time, You assign the XIB to people, and one person maintains part of it. Even if you use both, you need to understand that since you want the benefits of both, you will also introduce increased maintenance complexity.

I’m doing a lot of view writing in pure code. But if you write too much code, you’re never satisfied. You can’t override layoutSubviews all the time, so you think:

  1. Can you make it easier? Can you abstract out the layout logic?

  2. Write in a simple, readable XML like Android, and inherit?

  3. Can CSS be completely separate from HTML? Or even asynchronously load layouts?

With the Auto Layout

I’m sure I’m not the only one, you know? And so are many others. But what to do?

As the iOS platform has grown and matured, things have changed. IOS started out at 320 480, then came iPad, then 320 596, and then @2x, @3x and so on. Moreover, many apps now have to adapt to multiple languages, and the length of text in different languages may vary greatly. The practice of writing dead size has become increasingly difficult to use. How to use a general approach to layout? Apple came up with a universal solution, Auto Layout.

We used to write fixed size and origin, but now we can no longer use fixed numbers, otherwise there will be no way to unify. With what? Use relationship, for a variety of size screen, store fixed size, rather than storage relationship. Because for the same interface, in many cases, the relative relationship of subviews under different sizes is unchanged. We store relationships, and we let the program parse those relationships at run time, and we figure out what size to set, based on the current device, so that we can be consistent.

So how do we find these relationships? How do I abstract this out?

Let’s take a look at the code we wrote in layoutSubviews. The general code for layoutSubviews looks like this

 - (void)layoutSubviews {
    [super layoutSubviews];

    CGSize textSize = [_centerLabel.text sizeWithAttributes:@{NSFontAttributeName: _centerLabel.font}];
    _centerLabel.frame = CGRectMake((CGRectGetWidth(self.bounds) - textSize.width) / 2, (CGRectGetHeight(self.bounds) - textSize.height) / 2, textSize.width, textSize.height);
}Copy the code

If the origin and size of the frame of a view are separated into left, top, width, height

  CGFloat left = (1 / 2) * self.bounds.size.width - textSize.width / 2;
  CGFloat top = (1 / 2) * self.bounds.size.height - textSize.height / 2;
  CGFloat width = textSize.width;
  CGFloat height = textSize.height;
  _centerLabel.frame = CGRectMake(left, top, width, height);Copy the code

The left top, width and height are further mathematically transformed to separate variables and constants.

 CGFloat left = (1 / 2) * self.bounds.size.width - textSize.width / 2;
 CGFloat top = (1 / 2) * self.bounds.size.height - textSize.height / 2;Copy the code

If you have no variables, you assume a variable and multiply it by 0

 CGFloat width = 0 * self.bounds.size.width + textSize.width;
 CGFloat height = 0 * self.bounds.size.height + textSize.height;Copy the code

So, the code looks like this

 CGFloat left = (1 / 2) * self.bounds.size.width - textSize.width / 2;
 CGFloat top = (1 / 2) * self.bounds.size.height - textSize.height / 2;
 CGFloat width = 0 * self.bounds.size.width + textSize.width;
 CGFloat height = 0 * self.bounds.size.height + textSize.height;
 _centerLabel.frame = CGRectMake(left, top, width, height);Copy the code

Finally, left, top, width, height can be normalized to the following form

Aview. property = multiplier * bView. Property + constantCopy the code

And then, what if one view is associated with multiple views?

Let’s say it’s related to two views like this

AView. Property = multiplier * bView. Property + multiplier * cView. Attribute + constantCopy the code

How do you unify that?

No hurry, let’s look at the structure of CGRect first

struct CGRect {
    CGPoint origin;
    CGSize size;
};Copy the code

Rect has origin and size, size is the size of the view, how about origin? Origin is the position in the upper left corner of the view, size is absolute in the same set of units, origin is relative, but it’s relative to whom? Relative to its parent view, and because all views are on the same view tree, bView and cView can always find the same parent node, assuming F node, it will always get the following form

FView. Property = multiplier * bView. Property + constant FView. Property = multiplier * cView. Property + constantCopy the code

So it can also be converted to

Aview. property = multiplier * fview. property + constantCopy the code

Even if you add dView, eView, it doesn’t matter, it’s the same formula up here.

As you can see, all layout calculations can be mapped to such a one-time function

But what is the use of deriving this formula?

Let’s take a look at the Auto Layout API

/* Create constraints explicitly.  Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant" 
 If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
 */
+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;Copy the code

You see, this is the Auto Layout API. It’s based on this basic function operation. With this function, the dynamic layout code in layoutSubviews can be abstracted into declarative relationships!

Auto Layout is introduced

Auto Layout is a Layout technology introduced by Apple after iOS 6. It is mainly designed to accommodate multiple screens. The principle of Auto Layout itself is based on constraint, the implementation inside Auto Layout is hidden, and the interface design is very simple and clean, and hits the nail on the head.

Auto Layout is primarily a class called NSLayoutConstraint

The NSLayoutConstraint is primarily two apis.

  1. One is the one that we derived from above.

  2. There is also a Visual Format Language, parsing methods, and VFL is also created to express this functional relationship.

+ (NSArray<__kindof nslayoutconstraint="" *=""> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary *)metrics views:(NSDictionary *)views;Copy the code

In addition to methods, there are common attributes, as follows

typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
    NSLayoutAttributeLeft = 1,
    NSLayoutAttributeRight,
    NSLayoutAttributeTop,
    NSLayoutAttributeBottom,
    NSLayoutAttributeLeading,
    NSLayoutAttributeTrailing,
    NSLayoutAttributeWidth,
    NSLayoutAttributeHeight,
    NSLayoutAttributeCenterX,
    NSLayoutAttributeCenterY,
    NSLayoutAttributeBaseline,
    ...
    NSLayoutAttributeNotAnAttribute = 0
};Copy the code

So we’re done with the operations and properties of this basic function, and we’re done with the formulas. But it’s not over yet.

If you look closely, there are three types of NS size out relation, not only can you use an equal sign to determine both sides of the equation, but you can also use greater than or equal to, less than or equal to

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

Why are there two more? We used to do equality, but now we can do inequality. Amazing, isn’t it? For more details, check out the paper link at the end of the article.

Ok, let’s try it out! But when you write the code, you see it looks like this.

    CGSize textSize = [_centerLabel.text sizeWithAttributes:@{NSFontAttributeName: _centerLabel.font}];

    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:_centerLabel
                                                        attribute:NSLayoutAttributeCenterX
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeCenterX
                                                       multiplier:1
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:_centerLabel
                                                        attribute:NSLayoutAttributeCenterY
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeCenterY
                                                       multiplier:1
                                                         constant:0]]];
    [_centerLabel addConstraints:@[[NSLayoutConstraint constraintWithItem:_centerLabel
                                                                attribute:NSLayoutAttributeWidth
                                                                relatedBy:NSLayoutRelationEqual
                                                                   toItem:nil
                                                                attribute:NSLayoutAttributeNotAnAttribute
                                                               multiplier:0
                                                                 constant:textSize.width],
                                   [NSLayoutConstraint constraintWithItem:_centerLabel
                                                                attribute:NSLayoutAttributeHeight
                                                                relatedBy:NSLayoutRelationEqual
                                                                   toItem:nil
                                                                attribute:NSLayoutAttributeNotAnAttribute
                                                               multiplier:0
                                                                 constant:textSize.height]]];Copy the code

Well, it’s a good design, but it’s too complicated.

What’s the problem?

  1. The API and parameter names are too long

  2. The argument repeats and only sets the relationship between the two views, but _centerLabel appears several times

  3. When you look at the code again, your mind jumps, you try, when you look at the code, you go to the first item, and I see that I’ve set _centerLabel, and I look back and I’ve set centerX, what am I relative to? Self, self of what? Self’s centerX, so my mind keeps beating, and after I read it, I’m going to apply the parameters THAT I saw to the formula.

There’s a VFL. Try again

UIView *superview = self; NSDictionary *views = NSDictionaryOfVariableBindings(_centerLabel, superview); [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[superview]-(<=1)-[_centerlabel]" options:nslayoutformatalignallcentery="" metrics:nil="" views:views]]; ="" self="" addconstraints:[nslayoutconstraint="" constraintswithvisualformat:@"v:[superview]-(<="1)-[_centerLabel]" "="" options:nslayoutformatalignallcenterx="" views:views]]; <="" code="">Copy the code

All right, looks like we got a magic symbol. V: [superview] – (< = 1) – [_centerlabel] < strong = “” >, too not intuitive. You have to remember the meaning of each symbol. And there’s no auto-completion when you type a bunch of these magic symbols inside quotes with XCode. For those of you who are slow to write, like me, you have to pause for a moment and think about it once.

Why is it still not working?

VFL was designed with good intention to allow you to see the placement of interface elements through the introduction of symbols, such as horizontal lines – for spacing, and square brace-enclosed content [] for views. [superview]-[_centerLabel] Indicates that superView and _centerLabel are next to each other. [superView]-(<=1)-[_centerLabel]< strong=””> Indicates that the distance between two superViews and _centerLabel is greater than 1. V: Vertical, So what it means to be complete is that superview and _centerLabel are vertically next to each other with a distance greater than 1. Once explained, it makes sense, but the design does not escape the thought of solving equations. VFL expressions use symbols to represent operators and pass variables through views and metrics. API users are always thinking about what to do with this equation.

Coder Interface (API)

As a Coder, we often write User Interface to make the User more cool, so how do we design our own Coder Interface to make it more fun to use?

The design interface is the same as the product design, which needs to consider the user’s habit of thinking. The reason why NSLayoutContraint is so difficult to use is that it does not conform to the user’s habit of thinking. To paraphrase the product design phrase “Don’t make me think”, the NSLayoutConstraint API keeps making me think!

So, here comes the DSL

Before WE get to DSL, let’s talk about DSL. DSL is an abbreviation for Domain Specific Language, a Language whose basic purpose is to deal with a Specific problem in a certain Domain. It does not cover the entire problem domain like a general-purpose language, but rather deals with a specific problem. It is designed to transform the common language, make it more suitable for the user’s knowledge model, and use it smoothly. DSLS are common. CSS is a DSL for writing web layouts, and Podfile syntax is a DSL for writing Cocoapods dependency rules.

So what kind of DSL are we going to design?

When designing DSLS, you need to understand who you are targeting. The design of grammar itself is not the end, language is for the dissemination of thought service, is for the transformation of thinking, the knowledge content of other fields into the user’s familiar way of thinking, to adapt to the user’s thinking.

History has shown that there are as many DSLS as there are common ideas among programmers. In fact, the mechanism is there, good solutions will emerge. After AutoLayout was released, AutoLayout DSLS sprang up like mushrooms.

I found some representative ones, grouped according to the syntax style preferred by programmers

  1. State the command form as if it were a sentence

  2. Reduction of the mathematical formula, still plug in the formula calculation, but more intuitive

    1. Cartography

    2. CompactConstraint

  3. Shortcut form, which creates many short cut methods, covers common layout requirements

  4. Imitate CSS form, use CSS syntax to do iOS layout

Everyone can choose according to their own taste. But I finally choose navigation.

And the reason is, when we’re writing code, we’ve already figured out what we’re going to set up, and what we want to do is code it as directly as possible, as quickly as possible.

I’ll tell you what I’ll retrieve. If I want to center _centeLabel in its superView,

If you think about it mechanically

Make the center of _centerLabel equal to the center of the superview of _centerLabelCopy the code

Translated into code

make.center.equalTo(_centerLabel.superview);Copy the code

So that’s a straightforward statement. All the words I would retrieve would be a command I would understand, so I would post a few commands to retrieve the task. Then I would load the list into a block

[_centerLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  make.center.equalTo(_centerLabel.superview);
}];Copy the code

Then, the _centerLabel is centered. Isn’t that easy?

Masonry

The advantage of the DSL design for navigation is that it will translate mathematical functions into highly readable codes, which will be easy to read and easy to write, just like reading sentences. Moreover, it will not implement heavy grammars, and will rely entirely on Objective-C grammars. The operator acts as a concatenator, and the block acts as a list of Settings declarations. The nice thing about this is that it’s easy to implement, and you don’t need a separate Parser, so it doesn’t have a huge impact on efficiency. That’s the neat thing about this design, balancing requirements, users, the language itself, implementation complexity, performance, etc.

The navigation itself is only a syntax transformation, and no new functions are added on the basis of Auto Layout.

So the main API is also one

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;Copy the code

This is followed by the properties you can manipulate, which correspond to Auto Layout.

MASViewAttribute NSLayoutAttribute
view.mas_left NSLayoutAttributeLeft
view.mas_right NSLayoutAttributeRight
view.mas_top NSLayoutAttributeTop
view.mas_bottom NSLayoutAttributeBottom
view.mas_leading NSLayoutAttributeLeading
view.mas_trailing NSLayoutAttributeTrailing
view.mas_width NSLayoutAttributeWidth
view.mas_height NSLayoutAttributeHeight
view.mas_centerX NSLayoutAttributeCenterX
view.mas_centerY NSLayoutAttributeCenterY
view.mas_baseline NSLayoutAttributeBaseline

Then there are the three relations that also correspond to the Auto Layout definition

.equalTo equivalent to NSLayoutRelationEqual

.lessThanOrEqualTo equivalent to NSLayoutRelationLessThanOrEqual

.greaterThanOrEqualTo equivalent to NSLayoutRelationGreaterThanOrEqual

In addition, it has shortcut attributes to facilitate relative layout

Differences with Auto Layout

Navigation will feature two new apis, one for Update

- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;Copy the code

If you update the constant value in the Constraint, you don’t need to make it. You can update it

[_centerLabel mas_updateConstraints:^(MASConstraintMaker *make) {
    make.center.equalTo(_centerLabel.superview);
}];Copy the code

This sentence updates center, but does not change the dependency between the view and other views. So I would like to retrieve a similar contraints from the _centerLabel and update it instead of adding a new constraint

And this is spiced

- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;Copy the code

The difference is held to remove contraints previously set in the cabinet, so that the external code calling the navigation will not have to keep contraints reference for the purpose of removing contraints later.

Masonry using

Before we talk about the navigation system, let’s first compare and summarize iOS layout system. The layout system is a basic part of the GUI framework, and I will briefly analyze it from three basic dimensions

Back to CGFrame frame, frame is divided into size and Origin. If you compare the Size structure with the Android system, size has both structures, but Origin is different. Android has margin, and margin is different from Origin. Margin is relative to other view, can be flat level view, can be uneven, origin is only relative to the parent view, has nothing to do with other views. So if take several common Android Layout to do an explanation, iOS only native support AbsoluteLayout of Andorid.

Because of this basic design difference, iOS layout calculation can be reduced to a one-to-one function introduced just now.

Aview. property = multiplier * bView. Property + constantCopy the code

Margin has four edges, so the Layout code of Android is always used together with the LinearLayout and other layouts, otherwise the programmer will always have to deal with complex calculations. IOS does not need to deliberately highlight the Layout model, but it also needs such a model structure. So how does iOS implement a layout model like LinearLayout? With controls like UITtableView, UICollectionView, these UI controls do the complex calculations in the layout system.

Summarized in the following table

layout iOS Android Web
The data structure origin, size margin, size margin, size
The relative relations Relative to the parent view Now you can do either parent view or sibling view Relative to parent view or sibling view
Layout model UITableView, UICollectionView… LinearLayout, RelativeLayout… Posistion Attribute, float Attribute, Flexbox…

Masonry Demo

Since there is no native Layout model for iOS like Android, let’s implement some common layouts for Android to get familiar with the navigation

Android mainly has five layouts. We mainly implement the following three layouts: LinearLayout, FrameLayout and GridLayout. AbsoluteLayout is the default on iOS, so you don’t need to implement AbsoluteLayout. RelativeLayout itself addresses the problem of deeply nested layouts, not placement.

A LinearLayout is basically a list,

A vertical list



To implement vertical lists, mas_left, mas_right, and mas_height are fixed, and the value of mas_top is constantly adjusted. There are also top left right and bottom, but the margin is different from Android.

  1. The top left right bottom you use is not required to specify the top left right bottom of a margin relative to that element.

  2. The numbers here are directional, downward and right are positive, so the offset set right is -20 from right to left.

UIView *lastCell = nil; for (UIView *cell in _linearCells) { [cell mas_updateConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.mas_left).offset(20); make.right.equalTo(self.mas_right).offset(-20); make.height.equalTo(@(40));  if (lastCell) { make.top.equalTo(lastCell.mas_bottom).offset(20); } else { make.top.equalTo(@(20)); } }]; lastCell = cell; }Copy the code

The level of the list

To implement a horizontal list, mas_top mas_bottom and mas_width can be fixed, and the value of mas_left can be adjusted continuously.

UIView *lastCell = nil; for (UIView *cell in _cells) { [cell mas_updateConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@(20));  make.bottom.equalTo(@(-20)); make.width.equalTo(@(20));  if (lastCell) { make.left.equalTo(lastCell.mas_right).offset(20); } else { make.left.equalTo(@(20)); } }]; lastCell = cell; }Copy the code
FrameLayout

FrameLayout is a stack of layers, with layout_gravity setting the stack position.

We describe several of these stacks below

The upper left corner

When the properties of the view to be set are the same as the properties of the relative view, the properties of the relative view can be omitted.

 make.left.equalTo(cell.superview.mas_left);
 make.top.equalTo(cell.superview.mas_top);Copy the code

Can be written as

 make.left.equalTo(cell.superview);
 make.top.equalTo(cell.superview);Copy the code

It’s easy to center just use the Center property

make.center.equalTo(cell.superview);Copy the code

Top aligned left and right centered

Center can be divided into centerX and centerY. CenterX is used here with the top property

 make.centerX.equalTo(cell.superview);
 make.top.equalTo(cell.superview);Copy the code
GridLayout



Grid layout implementation is more complex, we need to calculate which row and column, constantly update left and top.

CGFloat cellWidth = 70; NSInteger countPerRow = 3; CGFloat gap = (self.bounds.size.width - cellWidth * countPerRow) / (countPerRow + 1); NSUInteger count = _cells.count; for (NSUInteger i = 0; i < count; i++) { UIView *cell = [_cells objectAtIndex:i]; NSInteger row = i / countPerRow; NSInteger column = i % countPerRow; [cell mas_updateConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@(row * (gap + cellWidth) + gap));  make.left.equalTo(@(column * (gap + cellWidth) + gap)); make.width.equalTo(@(cellWidth));  make.height.equalTo(@(cellWidth)); }]; }Copy the code

In addition, if you are tired of always typing mas_ prefix, define a macro when using it.

#define MAS_SHORTHAND
#import "Masonry"Copy the code

All right, isn’t that enough? But I’ll take you to the basics of navigation. For more information, check out the Navigation project.

SnapKit

For a new project you would like to retrieve, SnapKit is a version of Swift developed by the developers of Navigation. In addition, using the navigation in Swift may cause some inconvenience, such as the extra parentheses below

view3.mas_makeConstraints { make in
  self.animatableConstraints.extend([
       make.edges.equalTo()(superview).insets()(edgeInsets).priorityLow()(),
   ])
       make.height.equalTo()(view1.mas_height)
       make.height.equalTo()(view2.mas_height)
   }Copy the code

At the end

This article reviews the development of Layout technology on iOS platform, explains the origin of Auto Layout technology, the core of Auto Layout technology, and the generation of DSL technology. Finally, it introduces the use of DSL technology. As for the three questions raised at the beginning of the article, some of them have been answered, and the rest depends on the future development. In addition, the technologies I have not introduced, such as Size Class, are suitable for devices of different sizes with different designs. For example, when iPad and iPhone versions are available at the same time, the use of Size Class can bring better interactive experience, so I will not introduce it due to space limitations.

Finally, all the code in this article is on Github.

If you are interested in Layout related topics, you can continue to see the links below

  1. Cartography another excellent Auto Layout DSL

  2. Flexbox excellent CSS 3 layout model

  3. Classy – Expressive, flexible, and powerful stylesheets for native iOS apps

  4. Auto Layout explains the internal implementation

  5. The Cassowary Linear Arithmetic Constraint Solving Algorithm, Auto Layout internal Algorithm

The following is my blog, where I will update my iOS client and Hybird App development regularly. Subscribe!

A programmer who loves Tai Chi

Cat friends will

For the excellent Internet elite organization of Hubei/Central China by invitation, see cat Friends

Scan or long press to follow our wechat technology official number (mtydev)