Masonry以更漂亮的語法封裝了AutoLayout,是一個輕量級的布局框架。
Masonry有自己的布局DSL(domain-specific language),提供了一種鏈?zhǔn)綄懛▉砻枋鯪SLayoutConstraints,這使得布局代碼變得更加簡潔和可讀。
Masonry 支持 iOS 和 macOS。
NSLayoutConstraints怎么了?
在底層AutoLayout是一種很強(qiáng)大靈活的用于管理和布局視圖的方式。但用代碼創(chuàng)建約束(Constraints)顯得非常冗長且不易描述。設(shè)想這樣的簡單場景:在父視圖中填放一個子視圖,且每一邊留下10像素的邊距。
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
即使這樣簡單的一個例子,代碼就變得這么冗長,如果再多兩三個視圖,就立刻變得可讀性很差。
另外一個選擇是使用VFL,稍微不那么長和繞。但是ASKII類型的語法有它自己的隱患,并且動畫很麻煩因為NSLayoutConstraint constraintsWithVisualFormat:返回的是一個數(shù)組。
準(zhǔn)備好見你的Maker!
以下是用MASConstraintMaker創(chuàng)建的簡單約束:
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 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);
}];
還可以更短:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
不過需要注意在第一個例子中我們需要把約束添加到superview中,盡管Masonry會自動添加約束到合適的view中。
Masonry也會自動調(diào)用
view1.translatesAutoresizidngMaskIntoConstraints = NO;
不是所有的事情都是平等的
.equalTo 等價于 NSLayoutRelationEqual
.lessThanOrEqualTo 等價于 NSLayoutRelationLessThanOrEqual
.greaterThanOrEqualTo 等價于 NSLayoutRelationGreaterThanOrEqual
這3個等價關(guān)系接受一個參數(shù),可以是下面的任何一種:
1、MASViewAttribute
make.centerX.lessThanOrEqualTo(view2.mas_left);
以下是MASViewAttribute和NSLayoutAttribute 的對應(yīng)關(guān)系:
| 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 |
2. UIView/NSView
if you want view.left to be greater than or equal to label.left :
//這2中約束是等價的
make.left.greaterThanOrEqualTo(label);
make.left.greaterThanOrEqualTo(label.mas_left);
3. NSNumber
Auto Layout允許寬和高設(shè)置為常量。如果想設(shè)置view的最小和最大寬度,可以傳一個number值:
//width >= 200 && width <= 400
make.width.greaterThanOrEqualTo(@200);
make.width.lessThanOrEqualTo(@400);
然而Auto Layout 不允許left, right, centerY等的對齊屬性設(shè)置成常量值。所以,給這些屬性傳NSNumber,Masonry會轉(zhuǎn)換到view的superview中去,比如:
//creates view.left = view.superview.left + 10
make.left.lessThanOrEqualTo(@10)
除了NSNumber,也可以用原始值或結(jié)構(gòu)體來建立約束,如:
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(view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));
一般支持自動裝箱(autoboxing)的宏會有mas_前綴。沒前綴的版本在導(dǎo)入Masronry前,通過定義MAS_SHORTHAND_GLOBALS,也是可以用的。
4. NSArray
包含前面任何類型元素的數(shù)組也是可以的。
make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1, @100, view3.right]);
考慮優(yōu)先級(prioritize)
.priority 允許你指定一個確定的優(yōu)先級
.priorityHigh 等價于 UILayoutPriorityDefaultHigh
.priorityMedium 介于高優(yōu)先級和低優(yōu)先級之間
.priorityLow 等價于UILayoutPriorityDefaultLow
優(yōu)先級可以添加在約束鏈后面,像這樣:
make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow();
make.top.equalTo(label.mas_top).with.priority(600);
結(jié)構(gòu),結(jié)構(gòu),結(jié)構(gòu)
Masonry也提供了一些簡便方法來同時創(chuàng)建多種約束。這些被稱為MASCompositeConstraints
edges
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
size
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
center
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
為了可讀性高也可以把屬性連起來像這樣:
// All edges but the top should equal those of the superview
make.left.right.and.bottom.equalTo(superview);
make.top.equalTo(otherView);
讓美好持續(xù)發(fā)生
有時你需要修改已存在的約束來支持動畫或者移除、替換約束。在Masonry中有一些不同的方式來更新約束。
1、引用
可以通過創(chuàng)建變量或者屬性來引用一個特定的約束。也可以通過數(shù)組保存的方式來引用多個約束。
// in public/private interface
@property (nonatomic, strong) MASConstraint *topConstraint;
...
// when making constraints
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
...
// 然后你可以調(diào)用
[self.topConstraint uninstall];
2. mas_updateConstraints
如果只是更新約束中的常量值,你可以用這個mas_updateConstraints方法來代替mas_makeConstraints
// 這是蘋果推薦的用來添加或更新約束的地方
// 這個方法會在響應(yīng)setNeedsUpdateConstraints時被多次調(diào)用
// 會被UIKit內(nèi)部或者在你的代碼中當(dāng)你需要觸發(fā)約束的更新時調(diào)用
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
3、mas_remakeConstraints
mas_updateConstraints在更新一組約束時很有用,但是做一些更新約束值之外的事就顯得力不從心了。這就是為何要引入mas_remakeConstraints。
mas_remakeConstraints類似mas_updateConstraints,但是不同于更新常量值,它會在重新安裝約束前移除所有的約束。這就讓你能提供不同的約束,而不用時刻記住你要移除哪個約束了。
- (void)changeButtonPosition {
[self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(self.buttonSize);
if (topLeft) {
make.top.and.left.offset(10);
} else {
make.bottom.and.right.offset(-10);
}
}];
}
出現(xiàn)麻煩咋整!
布局并不能像你意料的那樣,所以當(dāng)事情真的搞砸了的時候,你肯定不希望在控制臺看到這樣的輸出:
Unable to simultaneously satisfy constraints.....blah blah blah....
(
"<NSLayoutConstraint:0x7189ac0 V:[UILabel:0x7186980(>=5000)]>",
"<NSAutoresizingMaskLayoutConstraint:0x839ea20 h=--& v=--& V:[MASExampleDebuggingView:0x7186560(416)]>",
"<NSLayoutConstraint:0x7189c70 UILabel:0x7186980.bottom == MASExampleDebuggingView:0x7186560.bottom - 10>",
"<NSLayoutConstraint:0x7189560 V:|-(1)-[UILabel:0x7186980] (Names: '|':MASExampleDebuggingView:0x7186560 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7189ac0 V:[UILabel:0x7186980(>=5000)]>
Masonry在NSLayoutConstraint增加了一個分類,覆蓋了*- (NSString )description的默認(rèn)實(shí)現(xiàn)。這樣在給約束和view起名字后,能夠很輕松的從中找出哪些是Masonry創(chuàng)建的約束。
這意味著你的控制臺輸出看起來是這樣的:
Unable to simultaneously satisfy constraints......blah blah blah....
(
"<NSAutoresizingMaskLayoutConstraint:0x8887740 MASExampleDebuggingView:superview.height == 416>",
"<MASLayoutConstraint:ConstantConstraint UILabel:messageLabel.height >= 5000>",
"<MASLayoutConstraint:BottomConstraint UILabel:messageLabel.bottom == MASExampleDebuggingView:superview.bottom - 10>",
"<MASLayoutConstraint:ConflictingConstraint[0] UILabel:messageLabel.top == MASExampleDebuggingView:superview.top + 1>"
)
Will attempt to recover by breaking constraint
<MASLayoutConstraint:ConstantConstraint UILabel:messageLabel.height >= 5000>
應(yīng)該在哪創(chuàng)建約束?
@implementation DIYCustomView
- (id)init {
self = [super init];
if (!self) return nil;
// --- Create your views here ---
self.button = [[UIButton alloc] init];
return self;
}
// 告訴UIKit你在使用AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// 這是蘋果推薦用來添加或更新約束的地方
- (void)updateConstraints {
// 在這里更新或者添加約束
[self.button remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
//根據(jù)蘋果介紹super方法應(yīng)該在方法的最后調(diào)用一下
[super updateConstraints];
}
- (void)didTapButton:(UIButton *)button {
// --- Do your changes ie change variables that affect your layout etc ---
self.buttonSize = CGSize(200, 200);
// 通知約束他們應(yīng)該被更新了
[self setNeedsUpdateConstraints];
}
@end
安裝:
使用CocoaPods.
在Podfile文件中寫:
pod ‘Masonry'
即可。
如果你在用Masonry時不想見到討厭的mas_前綴,就添加
#define MAS_SHORTHAND
到prefix.pch,并導(dǎo)入到Masonry中。
便捷代碼塊
將以下代碼塊拷貝到~/Library/Developer/Xcode/UserData/CodeSnippets,寫Masonry代碼爽到飛起:
mas_make -> [<view> mas_makeConstraints:^(MASConstraintMaker *make){<code>}];
mas_update -> [<view> mas_updateConstraints:^(MASConstraintMaker *make){<code>}];
mas_remake -> [<view> mas_remakeConstraints:^(MASConstraintMaker *make){<code>}];
特性:
1、不只是Auto Layout的子集,任何NSLayoutConstraint能做的,Masonry都能做
2、很棒的debug支持,只要你給約束和view取好有意義的名字
3、約束讀起來像自然語句,通俗易懂
4、沒有令人抓狂的宏魔法。Masonry有了宏不會污染全局的名字空間。
5、不基于字符串和字典,所以編譯的時候就能檢查出問題。