一起來學Masonry (一)
在看這篇文章之前作者已經默認了你已經有了自動布局的概念,如果對自動布局還是不了解的話,可以看下這篇文章: iOS使用autolayout和sizeclass 解決適配問題 。或許讀完這篇文章的話你會對自動布局有新的認識。
那么我們使用自動布局的途徑有哪些呢?
-
使用系統的約束
-
使用VFL語言
-
使用第三方 Masonry
-
使用第三方 SDAutoLayout
可能還有一些小眾的,這里不在多做介紹。
下面就對上面的四種方式先從寫法上做一些簡單比較。
1.使用系統提供的約束寫布局
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];
這段代碼的意思是:給blueview添加一條左邊的約束相對于redview左邊的約束。也就是說 blueview 的左邊和redview的左邊是對齊的。
等價于: blueview.left = redview.left *multiplier +constant.
2.使用vfl語言
NSString *vfl = @“V:|-5-[_view]-10-[_imageView(20)]-10-[_backBtn]-5-|";
這段代碼的意思是:在垂直方向從上到下,view離父視圖5點,imageView距離view 10點,同時imageView是20點高,backBtn離imageView底部10點,距離父視圖底部5點。
3.使用Masonry來布局
[self.pointImageview mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).with.offset(20);
make.top.equalTo(self.view).with.offset(22);
make.width.height.mas_equalTo(3);
}];
這是一個給pointImageview 添加一個完整約束的代碼 這段代碼的意思是:pointImageview 左邊距離self.view 20 頂部距離self.view 是22 寬高都是3
4.使用SDAutoLayout
view0.sd_layout
.leftSpaceToView(self.view, 10)
.topSpaceToView(self.view, 80)
.heightIs(100)
.widthRatioToView(self.view, 0.4);
這段代碼的意思是:view0 左邊距離self.view的左邊是10 頂部距離self.view的距離是80.高是100 寬是self.view的0.4倍。
通過上面的幾種寫法相信你對這幾種自動布局的方式也應該有所了解了。
-
使用系統的布局缺點是太繁瑣,沒條約束都要整這么一堆東西。優點:親生的。
-
使用VFL語言呢,蘋果為了簡化手寫Autolayout代碼所創建的專門負責編寫約束的代碼。缺點是:學習需要成本啊。并且不支持乘除。
-
使用masonry基于系統約束的封裝,代碼簡單易懂。需要引入第三方庫。
-
SDAutolayout 目前沒使用,不做評判。
這篇文章只是講一下masonry 的簡單使用,現在筆者所在公司就在使用masonry自動布局。所以,對這塊還是稍有了解的。
Masonry簡單使用
ok,下面開始masonry用法簡單介紹。首先我們應該看他提供給我們那些屬性和方法讓我們來調用。
Masonry屬性有哪些?
MASConstraint *left;左邊距
MASConstraint *top;上邊距
MASConstraint *right;右邊距
MASConstraint *bottom;底部
MASConstraint *leading;首部
MASConstraint *trailing;尾部
MASConstraint *width;寬
MASConstraint *height;高
MASConstraint *centerX;橫向中點
MASConstraint *centerY;縱向中點
說明一下:其中leading與left trailing與right 在正常情況下是等價的
一些不容易理解的點:
centerY 縱向中點
centerX 橫向中點
right 右邊距一般為負數
bottom 同理 距離底部的距離
約束的使用
添加約束:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
更改約束:
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
重置約束:
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
顧名思義:添加約束是:
mas_makeConstraints
就是說我們對一個視圖進行添加約束。
更新約束:
mas_updateConstraints
這里需要注意的是更新的話是對于原來約束的基礎上來說的。如果原來沒有你update的約束的話就會add一個約束。原則上來說,這里做的只是在原有約束的基礎上的改變。一般不更新沒有的約束。
重置約束:
mas_remakeConstraints
重置約束顧名思義就是把之前的約束全部刪掉,然后重新添加約束。這和make相比就多了一步,就是把已經存在的約束remove掉 重新添加約束。
其對應在masonry 中的源代碼是這樣的:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
相信這三段代碼是很容易理解的吧。add就是新添加。update 就是更新已經存在的約束。remake 就是把存在的約束remove掉 然后執行 make。
ok,下面就進入實戰吧。實戰提高的唯一途徑。想更好的理解就去使用。
下面就通過簡單的例子來向大家介紹Masonry對應屬性的用法。
在介紹用法之前 先看下 masonry 的語法。
我們如果要對一個view添加約束的話我們需要這樣來寫
UIView *redView = [UIView new];
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
// add the Constraints redView needed
}];
在添加約束之前我們要確定我們需要加約束的redView 是否存在 是否在已經被添加到父視圖。 也就是說我們要保證我們的redView 存在。只有存在的東西我們才能對其添加約束。 如果沒有addsubview 就添加約束就會崩潰。就相當于我們讓一個不存在的對象執行方法一樣,結果肯定會報錯。會crash。
看下面這段代碼:
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_top).with.offset(20);
make.top.and.right.equalTo(self.view).with.offset(0);
make.bottom.equalTo(self.view.mas_bottom).with.offset(-20);
}];
這段代碼的意思是:創建的redview 的左邊相對于self.view的左邊的距離是20. 其中的left 指的是視圖的左邊equalto 就是等于。()里就是相對于xxx 這里寫的是self.view 其實指的是self.view.mas_top .
with 在這里沒有什么特別的意思.offset 指的是偏移量。拿第一行代碼來說 創建redview 的左邊距 相對于 self.view 的左邊距的偏移量是20。
and 可以連接兩個約束,如果兩個約束相對視圖相同并且偏移量也相同的話這樣玩是沒有問題的。關于with和and 可以看源碼:
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
其實這兩個方法什么都沒做,但是用在這種鏈式語法中 就非常的巧妙和易懂 (我現在基本都會省略)
現在如果要我寫的話我會這樣寫:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(20);
make.top.right.mas_equalTo(0);
make.bottom.mas_equalTo(-20);
}];
看到這里你不禁會問 上面你用的equalto 這里為什么用mas_equalTo了呢? 其實這個問題提的很好。
下面會解釋一些這樣寫的原因.
先看代碼:
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
我們可以看到:mas_equalTo只是對其參數進行了一個BOX(裝箱) 操作,目前支持的類型:數值類型(NSNumber)、 點(CGPoint)、大小(CGSize)、邊距(UIEdgeInsets),而equalTo:這個方法不會對參數進行包裝。
其實上面對于redView的約束還可以這樣來寫。
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets (UIEdgeInsetsMake(0, 20, 20, 0));
}];
edges就是邊距 距離(top,left,bottom.right)
- (MASConstraint *)edges {
return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
這樣幾行代碼同樣能達到相同的效果。 ok,以上的代碼純粹是為了讓大家明白怎么來寫約束。以及約束的完整寫法和簡單寫法。下面就開始實戰吧,首先,咱們從簡單做起。
實現一:
我們需要創建一個view 視圖寬高固定都為50 距離頂部距離是100 距離父視圖左邊距離是10.
ok下面我們來實現:
UIView *blueView = [UIView new];
[blueView showPlaceHolder];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).with.offset(10);
make.top.equalTo(self.view.mas_top).offset(100);
make.width.mas_equalTo(@50);
make.height.mas_equalTo(50);
}];
效果圖 圖中藍色的view 就是我們所要創建的目的view
分析: 如果是相對于父視圖的話 我們可以這樣寫
make.top.mas_equalTo(10);
left 和top 的值 如果 相對的是一個視圖的left 和top 并且偏移量是一樣的話 我們可以這樣寫
// make.left.and.top.mas_equalTo(10); 我們的這個and 可以省略掉(上文提過這個)
make.left.top.mas_equalTo(10);
make.width 和make.height 等價于
make.size.mas_equalTo(CGSizeMake(50, 50));
其實我們在對這個view 加約束的時候我們可以發現我們添加了四個約束。這四個約束已經可以把這view 固定了。自動布局 不同于frame 之處在于,我們寫的是相對約束。我們想要固定一個視圖只需要對view添加約束,然后能把view 相對位置找到就ok,就像我之前寫的我理解的自動布局。(純屬個人理解)
這里姑且把我們的手機或者模擬器比作一個豎直方向放置的的黑板,我們的控件就相當于放在黑板上的一件東西,它可以用繩子固定在上面。我所理解的加約束就是:有一個物體懸空放到黑板上與黑板接觸然后用繩子能固定住,繩子不能用多也不能用少。用得多了浪費并且還可能會產生沖突。用得少了,不能正確的固定位置然后還可能導致在你移動的時候可能會找不到位置(就是說這個物體在你的約束下不見了)。
實現二:
- centerX舉例
UIView *orangeView = [UIView new];
[orangeView showPlaceHolder];
orangeView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:orangeView];
[orangeView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.top.equalTo(blueView.mas_top);
make.size.equalTo(blueView);
}];
- centerY舉例
UIView *yellowView = [UIView new];
[yellowView showPlaceHolder];
yellowView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:yellowView];
[yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(20);
make.centerY.mas_equalTo(-50);
make.left.equalTo(orangeView.mas_right).offset(5);
make.right.mas_equalTo(-20);
}];
- 相對位置 通過centerY來舉例
UIView *brownView = [UIView new];
[brownView showPlaceHolder];
[brownView setBackgroundColor:[UIColor brownColor]];
[self.view addSubview:brownView];
[brownView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(blueView);
make.size.mas_equalTo(CGSizeMake(60, 60));
make.left.equalTo(blueView.mas_right).offset(10);
}];
- center 舉例
UIButton *redButton= [UIButton buttonWithType:UIButtonTypeCustom];
redButton.backgroundColor = [UIColor redColor];
[redButton showPlaceHolder];
[self.view addSubview:redButton];
[redButton mas_makeConstraints:^(MASConstraintMaker *make) {
// make.centerX.mas_equalTo(0);
// make.centerY.mas_equalTo(0);
// 這兩句等價于
make.center.mas_equalTo(0);
make.size.mas_equalTo(CGSizeMake(50, 50));
}];
- Margin 舉例
UIButton *greenButton = [UIButton new];
[greenButton setBackgroundColor: [UIColor greenColor]];
[greenButton showPlaceHolder];
[self.view addSubview:greenButton];
greenButton.tag = 1006;
[greenButton addTarget:self action:@selector(buttonPressAction:) forControlEvents:UIControlEventTouchUpInside];
[greenButton mas_makeConstraints:^(MASConstraintMaker *make) {
//Magain 的
make.rightMargin.mas_equalTo(-8);
make.topMargin.mas_equalTo(20);
make.size.mas_equalTo(CGSizeMake(30, 30));
}];
效果圖如下: 相信通過效果圖你可以很清楚的看出centerx centery 以及center 的區別。 前面提到過centerx是橫向中點。比較上圖中 orangeview 和redview 便可以看出區別。orangeview 和redview 的centerx 是相同的。當然你可以根據需要來調整centerx 的偏移量來調整視圖的位置。對于centerx而言,左邊就是負值 右邊就是正值。 看下圖:
我們在寫約束的偏移量的時候一定要注意,約束是相對于哪個view,只有找好相對關系,才能設置正確的約束。一般而言,有這句口訣左負有正,上正下負。這句口訣是相對而言的,跟視圖的相對位置是有關系的。 比如說yellowview的centery在self.view 的centery的上面 所以就有了 make.centerY.mas_equalTo(-50);
總之,添加約束找相對值就一句話,把哪個view當做參照物,這個view對應的約束就是正值,對于參考系而言也就遵循了這句口訣:左負有正,上正下負。
關于Margin
magain是iOS8.0以后才出現的。UIView默認的layoutMargins的值為 {8, 8, 8, 8}。在iOS 8中,可以使用layoutMargins去定義view之間的間距,該屬性只對AutoLayout布局生效。在我們改變View的layoutMargins這個屬性時,會觸發
- (void)layoutMarginsDidChange
這個方法。我們在自己的View里面可以重寫這個方法來捕獲layoutMargins的變化。在大多數情況下,我們可以在這個方法里觸發drawing和layout的Update。
用處: 我們可以通過修改這個值來改變View之間的距離。
實現3: 循環創建視圖:
我們先看實現的效果圖然后再來分析是如何實現的。
效果圖:
UIButton *tempButton;
for (NSInteger i = 0; i < 5; i ++) {
UIButton *contentButton = [UIButton buttonWithType:UIButtonTypeCustom];
contentButton.backgroundColor = [UIColor purpleColor];
contentButton.tag = 1000 + i;
[contentButton setTitle:[NSString stringWithFormat:@"第%ld個",(long)i+1] forState:UIControlStateNormal];
[self.view addSubview:contentButton];
[contentButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(50, 30));
if (tempButton) {
make.left.equalTo(tempButton.mas_right).offset(10);
make.bottom.equalTo(tempButton.mas_bottom).offset(-10);
} else {
make.left.mas_equalTo(10);
make.bottom.mas_equalTo(-10);
}
}];
tempButton = contentButton;
}
如果我們想要循環創建view 的話,我們需要找到的就是一個相對值。首先,我們可能考慮到我們在創建第一個視圖的時候是相對于哪個視圖,我們創建第二個視圖的時候相對視圖又是誰。所以,為了實現我們想要找到的相對視圖。我們在這里引用了一個tempView這個view在第一次創建的時候是不存在的,所以我們就創建了一個view。然后對我們第一個視圖進行添加約束。第二次的時候我們已經有了tempView也就是說我們已經有了參照值就可以對其進行添加約束。以此類推。 我們可以參考下圖:
通過上面的圖我們可以看到:就是說我們在循環創建view的時候需要一個參照,所以我們引入一個tempview; 當我們第一次創建的時候 tempview不存在,所以我們contentview的約束就是相對于父視圖了。 創建完之后我們把創建的contentview賦給 tempview, 于是乎,tempview 翻身農奴把歌唱,苦日子終于熬到頭了,小三終于要當上正房了。現在有了tempview 當我們第二次進來的時候tempview已經存在了,我們創建的contentview 就有了參照物了。然后在循環體內不停的把contentview的值賦tempview 除了第一次,每次進來的時候都會有tempview 找到參照物,再加約束還不是收到擒來。。。
demo地址在這里 我是傳送門