Post

Masonry

Using a third-party Auto Layout library

Masonry

Anyone who has used Auto Layout knows how troublesome it is to write constraints in code. Although the VFL visual language reduces the amount of code to some extent, the code still looks awkward. Later I found this library: Masonry.

Masonry has many convenient shortcuts, for example:

Set height

1
2
3
4
5
	[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.height.equalTo(self.view);
    // Full form
       make.height.equalTo(self.view.mas_height);
    }];

Align left

1
2
3
4
5
	[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.leading.equalTo(self.view);
    // Full form
       make.height.equalTo(self.view.mas_leading);
    }];

Top constraint

1
2
3
4
5
	[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.top.equalTo(self.view);
    // Full form
       make.height.equalTo(self.view.mas_top);
    }];

x-axis center alignment

1
2
3
4
5
	[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.top.equalTo(self.view);
    // Full form
       make.height.equalTo(self.mas_centerX);
    }];

Concrete Examples

The examples below all use the shorthand syntax.

Multiplier constraints with multipliedBy

1
2
3
4
5
6
    [self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view);
        make.height.equalTo(self.view);
        make.top.equalTo(self.view);
        make.width.equalTo(self.view).multipliedBy(0.5);
    }];

The width of self.indicatorView is 0.5 times the width of self.view.

Alignment

1
2
3
4
5
6
7
8
    [self.passwordView mas_updateConstraints:^(MASConstraintMaker *make) {
    	// Align left
        make.leading.equalTo(self.passwordField);
        // Align right
        make.trailing.equalTo(self.passwordField);
        make.top.equalTo(self.passwordField.mas_bottom).offset(10);
        make.height.equalTo(@10);
    }];

passwordView is left and right aligned with passwordField.

Supporting iOS 6 and iOS 7

1
2
3
4
5
6
7
8
9
10
11
12
	[self.passwordTextField mas_makeConstraints:^(MASConstraintMaker *make) {
        make.leading.equalTo(@20);
        make.trailing.equalTo(@-20);
        if (IOS7) {
            UIView *topLayoutGuide = (id)self.topLayoutGuide;
            make.top.equalTo(topLayoutGuide.mas_bottom).with.offset(20);
        }
        else{
            make.top.equalTo(self.view.mas_top).with.offset(20);
        }
        make.height.equalTo(@36.0f);
    }];

self.topLayoutGuide is a UIView subclass, so it can be used for relative layout with that subclass.

Centering on the x-axis

1
2
3
4
5
6
7
8
9
10
11
	self.nextButton = [[UIButton alloc] init];
    self.nextButton.backgroundColor = [UIColor blackColor];
    [self.view addSubview:_nextButton];

    [self.nextButton mas_makeConstraints:^(MASConstraintMaker *make) {
        // Center on the x-axis
        make.centerX.equalTo(self.view);

        make.top.equalTo(self.mas_bottom).with.offset(200);
        make.width.equalTo(@100);
    }];

self.nextButton is aligned with its superview on the x-axis.

Same size as the superview

1
2
3
4
	UIEdgeInsets ed = UIEdgeInsetsMake(0, 0, 0, 0);
    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view).with.insets(ed);
    }];

You can even use it like this:

1
2
3
 [self.scrollView makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];

UIScrollView Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)viewDidLoad
{
 		self.scrollView = UIScrollView.new;
    self.scrollView.backgroundColor = [UIColor yellowColor];
    self.scrollView.pagingEnabled = YES;
    [self.view addSubview:_scrollView];
    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];

    UIView *view1 = [[UIView alloc] init];
    view1.backgroundColor = [UIColor grayColor];
    [self.scrollView addSubview:view1];

    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(@0);
        make.bottom.equalTo(@0);
        make.width.equalTo(@(self.view.frame.size.width));
        make.top.equalTo(@0);
    }];

    UIView *view2 = [[UIView alloc] init];
    view2.backgroundColor = [UIColor cyanColor];
    [self.scrollView addSubview:view2];

    [view2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(view1.mas_right);
        make.bottom.equalTo(@0);
        make.width.equalTo(@(self.view.frame.size.width));
        make.top.equalTo(@0);
        make.right.equalTo(@0);
    }];
}

As for how to lay out a UIScrollView: compared with view1, view2 has one extra constraint on the right side of the superview. In a normal layout, this constraint is actually unnecessary, because the first four constraints already determine the frame of view2. But in a UIScrollView, you must consider contentSize, which means the added subviews must determine the scroll range of the UIScrollView. Imagine that if this constraint were missing, after adding these two views to the UIScrollView, the scroll view could arbitrarily set contentSize to any width greater than self.view.frame.size.width * 2, and that would still be enough to contain the two views. So in that case, the entire UIScrollView would not really be constrained.

Therefore, when constraining a UIScrollView, if you want horizontal scrolling, you must have constraints on the left and right sides of the UIScrollView. Likewise, for vertical scrolling, you must have constraints on the top and bottom sides of the UIScrollView.

Tracking Constraints

1
@property (nonatomic, strong) MASConstraint *top;
1
2
3
4
5
6
[self.button mas_makeConstraints:^(MASConstraintMaker *make) {
		make.leading.equalTo(@0);
		make.trailing.equalTo(@0);
		self.top = make.top.equalTo(@80);
		make.height.equalTo(@50);
}];

Modify the constraint and animate it

1
2
3
4
self.top.mas_equalTo(@100);
[UIView animateWithDuration:0.3 delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
		[self.view.superview layoutIfNeeded];
}completion:NULL];

Stop tracking

1
[self.top uninstall];

Quickly Setting Width and Height

1
2
3
[self.topView mas_makeConstraints:^(MASConstraintMaker *make) {
		 make.size.mas_equalTo(self.view.frame.size);
}];

You can even do this:

1
2
3
[self.topView mas_makeConstraints:^(MASConstraintMaker *make) {
       make.size.mas_equalTo(self.view);
}];

Mixing Approaches

1
2
3
4
5
[self.view.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *obj, NSUInteger idx, BOOL *stop) {
			if (obj.firstItem == _otherView && obj.secondItem == _IDNumberFiled && obj.constant == 29.0f) {
					[self.view removeConstraint:obj];
			}
}];

Remove the constraints from IB and add Masonry constraints.

1
2
3
[self.otherView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(_IDNumberFiled.mas_bottom).offset(60.0f);
}];

This makes it easier to update constraints and animate changes.

1
2
3
4
5
6
[self.otherView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(_IDNumberFiled.mas_bottom).offset(100.0f);
}];
[UIView animateWithDuration:0.3 delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
    [self.view layoutIfNeeded];
}completion:NULL];

Finally

As for the difference between NSLayoutAttributeLeading / NSLayoutAttributeTrailing and NSLayoutAttributeLeft / NSLayoutAttributeRight: in right-to-left locales such as Hebrew, leading and trailing are reversed, so leading becomes the right side and trailing becomes the left side, while left/right always refer to the physical left and right sides. Also, look at the following two ways of writing it:

1
2
3
4
5
6
7
[self.otherView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.view.contentView);
}];
// Or
[self.otherView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(@0.0f);
}];

I personally recommend using the second approach, because the first one makes it very easy to reference the wrong superview. For example, otherView may be a subview of self.view.contentView, but you might accidentally write self.view instead. In that case, Masonry will not issue any warning.

This post is licensed under CC BY 4.0 by the author.