Masonry是一個輕量級的布局框架,它擁有自己的描述語法(采用更優雅的鏈式語法封裝)來自動布局,具有很好可讀性且同時支持iOS和Max OS X等。
總之,對于側重寫代碼的coder,請你慢慢忘記Frame,喜歡Masonry吧
[TOC]
常用的屬性與常量
-
MASViewAttribute 以對應的系統類型
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
-
UIView
先來一波最為常用的使用方法,大家可以看一下大致語法,下面會細講使用
//分別設置各個相對邊距(superview為view的父類視圖,下同) make.left.mas_equalTo(superView.mas_left).mas_offset(10); make.right.mas_equalTo(superView.mas_right).mas_offset(-10); make.top.mas_equalTo(superView.mas_top).mas_offset(10); make.bottom.mas_equalTo(superView.mas_bottom).offset(-10); //直接連接使用left大于等于某個值 make.left.mas_greaterThanOrEqualTo(10); //設置寬和高 make.width.mas_equalTo(60); make.height.mas_equalTo(60); //.設置center和寬高比 make.center.mas_equalTo(superView); make.width.mas_equalTo(superView).multipliedBy(1.00/3); make.height.mas_equalTo(superView).multipliedBy(0.25); //.關于約束優先級,此處要注意約束沖突的問題,統一約束優先級大的生效 make.left.mas_equalTo(100); make.left.mas_equalTo(view.superview.mas_left).offset(10); make.left.mas_equalTo(20).priority(700); make.left.mas_equalTo(40).priorityHigh(); make.left.mas_equalTo(60).priorityMedium(); make.left.mas_equalTo(80).priorityLow(); //如果你想讓view的(x坐標)左邊大于等于label的左邊,以下兩個約束的寫法效果一樣 make.left.greaterThanOrEqualTo(label); make.left.greaterThanOrEqualTo(label.mas_left);
注:約束的鏈式寫法中,不包含其他相對的view時,默認為其superview,即
make.left.mas_equalTo(100);
等價于make.left.mas_equalTo(view.superview.mas_left).offset(10);
和make.left.mas_equalTo(view.superview).offset(10);
-
更加便利的約束方法
Masonry提供了一些便利的方法供我們同時創建多個不同的約束,他們被稱為MASCompositeConstraints,如:
edges:
// 使一個view的top, left, bottom, right 等于view2的 make.edges.equalTo(view2); //相對于superviewde上左下右邊距分別為5,10,15,20 make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
size:
// 使得寬度和高度大于等于 titleLabel make.size.greaterThanOrEqualTo(titleLabel) // 相對于superview寬度大100,高度小50 make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
center:
//中心與button1對齊 make.center.equalTo(button1) //水平方向中心相對向左偏移5,豎直方向中心向下偏移10 make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
你可以在約束鏈里添加相應的view來增加代碼的可讀性:
// 除了top,所有的邊界與superview對齊 make.left.right.and.bottom.equalTo(superview); make.top.equalTo(otherView);
-
NSNumber
自動布局允許使用常量去設置寬或高,如果你想通過一個數字設置一個view的最小和最大的width,可以用equality blocks,如下:
//width >= 200 && width <= 400 make.width.greaterThanOrEqualTo(@200); make.width.lessThanOrEqualTo(@400)
然而自動布局不允許對齊屬性的約束(如:left,right,centerY等)設置為常量值,你可以使用NSNumber來設置相對于父類view這些約束屬性,如:
// creates view.left = view.superview.left + 10 make.left.lessThanOrEqualTo(@10)
如果你不想使用NSNumber來設置,也可以用如下結構來創建你的約束,如:
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));
-
NSArray
用數組添加集中不同類的約束,如:make.height.equalTo(@[view1.mas_height, view2.mas_height]); make.height.equalTo(@[view1, view2]); make.left.equalTo(@[view1, @100, view3.right]);
-
常見約束的各種類型
/** 1.尺寸:width、height、size 2.邊界:left、leading、right、trailing、top、bottom 3.中心點:center、centerX、centerY 4.邊界:edges 5.偏移量:offset、insets、sizeOffset、centerOffset 6.priority()約束優先級(0~1000),multipler乘因數, dividedBy除因數 */
Masonry使用注意
使用
mas_makeConstraints
方法的元素必須 事先 添加到父元素中,例如[self.view addSubView:view]
;-
mas_equalTo
和equalTo
的區別:-
-
equalTo
:僅支持基本類型; -
mas_equalTo
:支持類型轉換,支持復雜類型。是對equalTo的封裝。支持CGSize CGPoint NSNumber UIEdgeinsets。
以下實現的是相同的效果:
make.width.equalTo(@100);
跟make.width.mas_equalTo(100);
-
-
-
mas_equalTo
是一個Macro,比較 值; -
equalTo
比較View。
以下實現的是相同的效果
make.bottom.mas_equalTo(ws.view.mas_bottom);
跟make.bottom.equalTo(ws.view);
mas_equalTo
比equalTo
多了類型轉換操作,大多數時候兩個方法是 通用的。但是- 對于數值元素使用
mas_equalTo
; - 對于對象或多個屬性的處理,使用
equalTo
;(特別的多個屬性時,必須使用equalTo
,例如make.left.and.right.equalTo(self.view)
)
-
-
-
去掉mas_前綴,只用equalTo,只需要把下面代碼添加到.prefix文件:
// 只要添加了這個宏,就不用帶mas_前綴(`equalTo`就等價于`mas_equalTo`) #define MAS_SHORTHAND // 對于默認的約束參數自動裝箱 #define MAS_SHORTHAND_GLOBALS // 這個頭文件,一定要放在上面兩個宏的后面 #import "Masonry.h"
-
注意點方法
with
和and
,這兩個方法其實沒有做任何操作,方法只是返回對象本身,這個方法的作用,完全是為了可讀性。make.left.and.right.equalTo(self.view);
和make.left.right.equalTo(self.view);
是完全一樣的,但是加了and
方法的語法,可讀性更好。 multipliedBy
的使用只能是設置同一個控件的,比如這里的bottomInnerView
,make.height.mas_equalTo(bottomInnerView.mas_width).multipliedBy(3);
-
簡化:
[iconView makeConstraints:^(MASConstraintMaker *make){ make.top.equalTo(self.view).with.offset(30); make.left.equalTo(self.view).with.offset(30); make.bottom.equalTo(self.view).with.offset(-30); make.right.equalTo(self.view).with.offset(-30); }]
可以簡化為
make.top.left.bottom.and.right.equalTo(self.view).with.insets(UIEdgeInsetsMake(10,10,10,10));
或
make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(30.30.30.30));
其中
leading
與left trailing
與right
在正常情況下是等價的,但是在一些布局是從右至左時(比如阿拉伯文),則會對調,所以基本可以不理不用。用left
和right
就好。用leading
或trailing
后就不要用left
或right
,如果混用會出現崩潰。對
label
的約束比必須設置最大的約束寬度;
self.titleLabel.preferredMaxLayoutWidth = w - 100;
因為iOS中原點在左上角所以注意使用offset時注意right和bottom用負數
使用
Masonry
不需要設置控件的translatesAutoresizingMaskIntoConstraints
屬性為NO
,(錯誤觀點:為防止block
中的循環引用,使用弱引用),因為在這里block
是局部的引用,block
內部引用self
不會造成循環應用的。(沒必要的寫法:__weak typeof (self) weakSelf = self;
)-
Masonry約束控件出現沖突的問題:當約束沖突發生的時候,我們可以設置
view
的key
來定位是哪個view。
比如:redView.mas_key = @"redView"; greenView.mas_key = @"greenView"; blueView.mas_key = @"blueView";
如果覺得這樣一個個設置比較繁瑣,
Masonry
提供了批量設置的宏:// 一句代碼即可全部設置 MASAttachKeysMASAttachKeys(redView,greenView,blueView);
約束的優先級
-
.priority
允許你指定一個精確的優先級,數值越大優先級越高,最高1000
。-
priorityHigh
等價于UILayoutPriorityDefaultHigh
,優先級值為750
。 -
priorityMedium
介于高優先級和低優先級之間,優先級值在250~750
之間。 -
priorityLow
等價于UILayoutPriorityDefaultLow
,優先級值為250
。
-
優先級可以在約束的尾部添加:
make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow();
make.top.equalTo(label.mas_top).with.priority(600);
Greater/Less
一般與Priority
一起使用,為一個Constraint
設置了Greater/Less
后,調整Priority
。如果Constraint
的Priority
的值越大,程序優先設置它的Constraint
效果。
Masonry添加約束的方法
-
這個方法只會添加新的約束:
[view makeConstraints:^(MASConstraintMaker *make) { }];
-
這個方法將會覆蓋以前的某些特定的約束
[view updateConstraints:^(MASConstraintMaker *make) { }];
-
這個方法會將以前的所有約束刪掉,添加新的約束
``` [view remakeConstraints:^(MASConstraintMaker *make) { }]; ```
- 里面覺得最好用的是masonry_remakeConstraints,保證不會錯,
-
要記得將約束寫在
updateConstraints
里面:- (void) updateConstraints{ }
然后調用下面這一串:
[self setNeedsUpdateConstraints]; [self updateConstraintsIfNeeded]; [self layoutIfNeeded];
否則不會更新。
-
setNeedsLayout:
-
setNeedsLayout:
告知頁面需要刷新,但是不會立刻開始更新。執行后會立刻調用layoutSubviews
。 -
layoutIfNeed:
告知頁面布局立刻更新。所以一般都會和setNeedsLayout
一起使用。如果希望立刻生成新的frame
需要調用此方法。利用這點:一般布局動畫可以在更新布局后直接使用這個方法讓動畫生效。 -
layoutSubViews:
系統重寫布局setNeedsUpdateConstraints:
告訴需要更新約束,但是不會立刻開始。 -
updateConstraintsIfNeeded:
告知立刻更新約束。 -
updateConstraints:
系統更新約束。
-
-
修改約束
有時候,你為了實現動畫或者移除替換一些約束時,你需要去修改一些已經存在的約束,Masonry提供了一些不同的方法去更新約束,你也可以將多個約束存在數組里。
-
References
你可以持有某個特定的約束,讓其成為成員變量或者屬性
//設置為公共或私接口
@property (nonatomic, strong) MASConstraint *topConstraint; ... // 添加約束 [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); }]; ... // 然后可以調用 //該約束移除 [self.topConstraint uninstall]; //重新設置value,最常用 self.topConstraint.mas_equalTo(20); //該約束失效 [self.topConstraint deactivate]; //該約束生效 [self.topConstraint activate];
-
mas_updateConstraints
如果你只是想更新一下view對應的約束,可以使用 mas_updateConstraints 方法代替 mas_makeConstraints方法
//這是蘋果推薦的添加或者更新約束的地方
// 在響應setNeedsUpdateConstraints方法時,這個方法會被調用多次
// 此方法會被UIKit內部調用,或者在你觸發約束更新時調用
- (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); }]; //調用super [super updateConstraints]; }
-
mas_remakeConstraints
mas_updateConstraints只是去更新一些約束,然而有些時候修改一些約束值是沒用的,這時候mas_remakeConstraints就可以派上用場了
mas_remakeConstraints某些程度相似于mas_updateConstraints,但不同于mas_updateConstraints去更新約束值,他會移除之前的view的所有約束,然后再去添加約束
- (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); } }];
-
蘋果官方建議:添加/更新約束在
updateConstraints
這個方法內// this is Apple's recommended place for adding/updating constraints - (void)updateConstraints { //更新約束 [self.btn 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]; }
在哪創建我的約束
貼一個官方說明的例子:
@implementation DIYCustomView
- (id)init {
self = [super init];
if (!self) return nil;
// --- Create your views here ---
self.button = [[UIButton alloc] init];
return self;
}
// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
// --- remake/update constraints here
[self.button remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapButton:(UIButton *)button {
// --- Do your changes ie change variables that affect your layout etc ---
self.buttonSize = CGSize(200, 200);
// tell constraints they need updating
[self setNeedsUpdateConstraints];
}
創建約束技巧:
-
多個(2個以上)控件的等間隔排序顯示
/** * axisType 軸線方向 * fixedSpacing 間隔大小 * fixedItemLength 每個控件的固定長度/寬度 * leadSpacing 頭部間隔 * tailSpacing 尾部間隔 * */ //1. 等間隔排列 - 多個控件間隔固定,控件長度/寬度變化 - (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing; //2. 等間隔排列 - 多個固定大小固定,間隔空隙變化 - (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
-
多行label的約束問題
//創建label self.label = [UILabel new]; self.label.numberOfLines = 0; self.label.lineBreakMode = NSLineBreakByTruncatingTail; self.label.text = @"有的人,沒事時喜歡在朋友圈里到處點贊,東評論一句西評論一句,比誰都有存在感。等你有事找他了,他就立刻變得很忙,讓你再也找不著。真正的朋友,平常很少聯系。可一旦你遇上了難處,他會立刻回復你的消息,第一時間站出來幫你。所謂的存在感,不是你有沒有出現,而是你的出現有沒有價值。存在感,不是刷出來的,也不是說出來的。有存在感,未必是要個性鋒芒畢露、甚至鋒利扎人。翩翩君子,溫潤如玉,真正有存在感的人,反而不會刻意去強調他的存在感。他的出現,永遠都恰到好處。我所欣賞的存在感,不是長袖善舞巧言令色,而是對他人的真心關照;不是鋒芒畢露計較勝負,而是讓人相處得舒服;不是時時刻刻聒噪不休,而是關鍵時刻能挺身而出。別總急著出風頭,希望你能有恰到好處的存在感。"; [self addSubview: self.label]; [self.label makeConstraints:^(MASConstraintMaker *make) { make.left.top.equalTo(10); make.right.equalTo(-10); }]; //添加約束 - (void)layoutSubviews { //1. 執行 [super layoutSubviews]; [super layoutSubviews]; //2. 設置preferredMaxLayoutWidth: 多行label約束的完美解決 self.label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20; //3. 設置preferredLayoutWidth后,需要再次執行 [super layoutSubviews]; //其實在實際中這步不寫,也不會出錯,官方解釋是說設置preferredLayoutWidth后需要重新計算并布局界面,所以這步最好執行 [super layoutSubviews]; }
-
UIScrollView的問題
原理同自動布局一樣 UIScrollView上添加UIView
UIView上添加需要顯示的控件 UIScrollView滾動高度取決于顯示控件的總高度
對子控件做好約束,可達到控制UIView的大小//創建滾動視圖 UIScrollView *scrollView = [UIScrollView new]; self.scrollView = scrollView; scrollView.backgroundColor = [UIColor grayColor]; [self addSubview:scrollView]; [self.scrollView makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self); }]; [self setUpContentView]; //添加內容視圖 - (void)setUpContentView { //約束UIScrollView上contentView UIView *contentView = [UIView new]; [self.scrollView addSubview:contentView]; [contentView makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.scrollView); make.width.equalTo(self.scrollView); //此處必填 - 關鍵點 }]; //添加控件到contentView,約束原理與自動布局相同 UIView *lastView; CGFloat height = 30; for (int i = 0; i <1 5; i ++) { UIView *view = UIView.new; view.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 256.0 green:arc4random() % 255 / 256.0 blue:arc4random() % 255 / 256.0 alpha:1.0]; [contentView addSubview:view]; [view makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(lastView ? lastView.bottom : @0); make.left.equalTo(0); make.width.equalTo(contentView.width); make.height.equalTo(height); }]; height += 30; lastView = view; } [contentView makeConstraints:^(MASConstraintMaker *make) { make.bottom.equalTo(lastView.bottom); }]; }