(0068) Masonry Experience of AutoLayout Framework for iOS Development

Posted by RandomEngy on Wed, 22 May 2019 01:51:23 +0200

Apple officials gave some suggestions on automatic layout

These suggestions are applicable regardless of whether you use interfaceBuilder or code to achieve automatic layout.
(1) Specify the shape of the view without the frame, bounds, and center of the view
(2) Layout with stackView whenever possible
(3) Constraints should be established as far as possible between view and its adjacent view.
(4) Avoid specifying fixed lengths and widths for view s
(5) Be careful when updating the frame of view automatically, especially for view with insufficient constraints.
(6) The naming of view should be meaningful and easy to recognize when laying out.
(7) Use learning and trailing constraints, not left and right.
(8) When writing constraints relative to the view boundary, there are two cases:
Horizontal constraints: For most controls, constraints should be relative to the internal boundaries of the root view
For stories like fiction Read In a case where text is full of screens, constraints should be relative to text boundaries.
For views that need to be paved with the width of the root view, constraints can be relative to the boundaries of the root view.
Vertical constraints: If the root view is partially occluded by the navigation bar, tabBar, etc., then the constraints should be relative to top margin and bottom margin.
(9) When using autolayout to lay out view s created with code, set their translates Autoresizing MaskInto Constraints attribute to NO. If this property is set to YES, system Constraints are automatically generated for these view s, which may conflict with the constraints we set.


Experience of AutoLayout Framework Masonry

At the group sharing meeting, we shared some ideas about page layout and mentioned AutoLayout halfway. After the meeting, I decided to fill in a pit I dug a long time ago. (There are still many pits, let alone fill in more images.)

Masonry is the first available framework. You can see why Masonry is chosen and see the official documents. https://github.com/SnapKit/Masonry Officially, AutoLayout supports all features of Masonry. I used Masonry for the interface of this project.


Some Basic Concepts of AutoLayout

  • Constraints are used to control the size and position of the view. The system calculates the frame by setting constraints and draws the screen at runtime.
  • Two attributes Content Compression Resistance (crowding, the higher the value, the more fixed) and Content Hugging (hugging), the Masonry code is as follows
//content hugging is 1000
[view setContentHuggingPriority:UILayoutPriorityRequired
                           forAxis:UILayoutConstraintAxisHorizontal];

//content compression is 250
[view setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
                                         forAxis:UILayoutConstraintAxisHorizontal];
  • The multipler attribute represents the percentage of constrained objects whose constraints are constrained. In Masonry, there is a corresponding multipliedBy function.
//20% of the width of the superView
make.width.equalTo(superView.mas_width).multipliedBy(0.2);
  • UILabel Settings Multi-line Computing Under AutoLayout Needs PreerredMaxLayoutWidth Settings
label.preferredMaxWidth = [UIScreen mainScreen].bounds.size.width - margin - padding;
  • Preerred Max Layout Width is used to set the maximum width, usually in a multi-line UILabel.
  • System Layout Size FittingSize method can obtain view height
  • IOS 7 has two useful attributes, topLayoutGuide and bottomLayoutGuide, which are mainly for easy access to the head view area and bottom view area of UINavigation Controller and UITabBarController.
//Masonry directly supports this attribute
make.top.equalTo(self.mas_topLayoutGuide);

AutoLayout's Differences on Several Updating Methods

  • SetNeeds Layout: Tell the page that it needs to be updated, but not immediately. Layout Subviews are called immediately after execution.
  • Layout IfNeeded: Tell the page layout to be updated immediately. So it's usually used with setNeeds Layout. If you want to generate a new frame right away, you need to call this method. With this general layout animation, you can use this method directly after updating the layout to make the animation work.
  • Layout Subviews: System Rewrite Layout
  • SetNeeds Update Constraints: Notify that constraints need to be updated, but not immediately
  • Update Constraints IfNeeded: Notify immediate updates to constraints
  • Update Constraints: System Update Constraints

Notes for Masonry Use

  • The view using mas_makeConstraints needs to be added Subview before using this method
  • mas_equalTo applies to numeric elements, and equalTo applies to multiple attribute ratios such as make.left.and.right.equalTo(self.view)
  • Methods and with are just for readability and return to themselves, such as make.left.and.right.equalTo(self.view) and make.left.right.equalTo(self.view) are the same.
  • Because the origin in iOS is in the upper left corner, pay attention to the negative number of right and bottom when using offset.

Problems to be noted when Masonry adapts iOS 6 and iOS 7

The development project was debugged on iOS 8 first. The test found that the low version of the system would crash. After repair, the problem was mainly caused by the object of equalTo pointing to the parent view of the parent view or the child view of the same level of the parent view. Therefore, 90% of the runaway problem occurred when constraints were made because of this.

Masonry usage paradigm

Basic writing method

//Margin relative to parent view is 10
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

//Simple Writing with Margin of 10 Relative to Parent View
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

//These two functions are exactly the same.
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.greaterThanOrEqualTo(self.view);
    make.left.greaterThanOrEqualTo(self.view.mas_left);
}];

//EquaTo. Less Than OrEqual To. Greater Than OrEqual To uses NSNumber
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.width.greaterThanOrEqualTo(@200);
    make.width.lessThanOrEqualTo(@400);
    make.left.lessThanOrEqualTo(@10);
}];

//If you can use the previous data structure without using NSNumber, just use mas_equalTo.
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(42);
    make.height.mas_equalTo(20);
    make.size.mas_equalTo(CGSizeMake(50, 100));
    make.edges.mas_equalTo(UIEdgeInsetsMake(10, 0, 10, 0));
    make.left.mas_equalTo(self.view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));
}];

//Arrays can also be used
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.height.equalTo(@[self.view.mas_height, superview.mas_height]);
    make.height.equalTo(@[self.view, superview]);
    make.left.equalTo(@[self.view, @100, superview.mas_right]);
}];

// The Use of priority
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.greaterThanOrEqualTo(self.view.mas_left).with.priorityLow();
    make.top.equalTo(self.view.mas_top).with.priority(600);
}];

//Create multiple constraints at the same time
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
    //Let top,left,bottom,right be the same as self.view
    make.edges.equalTo(self.view);
    //edges
    make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(5, 10, 15, 20));
    //size
    make.size.greaterThanOrEqualTo(self.view);
    make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50));
    //center
    make.center.equalTo(self.view);
    make.center.equalTo(self.view).centerOffset(CGPointMake(-5, 10));
    //chain
    make.left.right.and.bottom.equalTo(self.view);
    make.top.equalTo(self.view);
}];

How to calculate the height of UITableView in AutoLayout case

The main reason is that the height of UILabel will change, so here's how to deal with the change of UILabel. When setting UILabel, pay attention to setting the width of preferred Max Layout Width. ContentHugging Priority is UILayout Priority Requried.

CGFloat maxWidth = [UIScreen mainScreen].bounds.size.width - 10 * 2;

textLabel = [UILabel new];
textLabel.numberOfLines = 0;
textLabel.preferredMaxLayoutWidth = maxWidth;
[self.contentView addSubview:textLabel];

[textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(statusView.mas_bottom).with.offset(10);
    make.left.equalTo(self.contentView).with.offset(10);
    make.right.equalTo(self.contentView).with.offset(-10);
    make.bottom.equalTo(self.contentView).with.offset(-10);
}];

[_contentLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

If the version supports the minimum version of iOS 8 or more, you can directly use UITableView AutomaticDimension to return directly to the tableview's heightForRowAtIndexPath.

tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = 80; //Reduce the first computation, iOS 7 support

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Just return this!
    return UITableViewAutomaticDimension;
}

But if you need to be compatible with previous versions of iOS 8, you have to go back to the old way, mainly with system Layout Size Fitting Size. The first step is to add a height attribute to the data model to cache the height, then static an instance of Cell initialized only once in the heightForRowAtIndexPath agent of table view, then fill the data according to the content of the model, and finally get the cell height according to the system Layout Size FittingSize method of the cell contentView. The code is as follows

//Add attribute cache height to model
@interface DataModel : NSObject
@property (copy, nonatomic) NSString *text;
@property (assign, nonatomic) CGFloat cellHeight; //Cache height
@end

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    static CustomCell *cell;
    //Initialize cell only once
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([CustomCell class])];
    });
    DataModel *model = self.dataArray[(NSUInteger) indexPath.row];
    [cell makeupData:model];

    if (model.cellHeight <= 0) {
        //Use System Layout Size FittingSize to get height
        model.cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1;
    }
    return model.cellHeight;
}

animation

Because layout constraints are to break away from the expression of frame, but animation needs to be carried out according to this, there will be some contradictions, but according to the principle of layout constraints mentioned above, at a certain time constraints will be restored to frame so that the view can be displayed, this time can be controlled by layoutIfNeeded method. The code is as follows

[aniView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.bottom.left.right.equalTo(self.view).offset(10);
}];

[aniView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.view).offset(30);
}];

[UIView animateWithDuration:3 animations:^{
    [self.view layoutIfNeeded];
}];

Reference articles

Topics: iOS Attribute Session github