masonry是基于UILayoutConstraint封裝的第三方布局框架,相比于UILayoutConstraint添加約束的代碼量,masonry運用鏈式編程的思想可謂是極簡單和優雅的。
本文所使用的所有代碼在這里
文章目錄機構結構:
1、不同方式代碼添加約束的比較
2、masonry的基本用法(添加/更新/重置約束、動畫效果)
3、masonry的使用技巧(多控件相對父控件居中、等間距分布,約束UIScrollView技巧)
4、UITableViewCel自適應高度介紹
一、幾種代碼添加約束的比較
首先先創建一個View視圖,并將視圖添加到父視圖上。
// 創建一個子視圖,添加到父視圖上面
UIView *view= [[UIView alloc] init];
view.backgroundColor = [UIColor redColor];
//給視圖添加約束之前必須先將該視圖添加到俯視圖上否則會crash
[self.view addSubview:view];
接下來我們將通過三種方法給view添加約束,以達到距離self.view的頂部為100、左右各為20,高度為50.
1、系統方法
// 1、禁用autoresizing
view.translatesAutoresizingMaskIntoConstraints = NO;
// 2、創建約束對象
//頂部約束(基于父控件)
NSLayoutConstraint *topCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
// 左邊約束(基于父控件)
NSLayoutConstraint *leftCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:20];
// 右邊約束(基于父控件)
NSLayoutConstraint *rightCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-20];
// 高度約束(自身)
NSLayoutConstraint *heightCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:50];
// 3、 添加約束
[self.view addConstraints:@[topCos,leftCos,rightCos]];
[view addConstraint:redHeightCos];
2、VFL語言添加約束
//禁用autoresizing
view.translatesAutoresizingMaskIntoConstraints = NO;
// 水平方向
NSArray *hCos = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[view]-20-|" options:kNilOptions metrics:nil views:NSDictionaryOfVariableBindings(view)];
[self.view addConstraints:hCos];
//豎直方向
NSArray *vCos = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(==50)]" options:kNilOptions metrics:nil views:NSDictionaryOfVariableBindings(view)];
[self.view addConstraints:vCos];
3、masonry添加約束
[view mas_makeConstraints:^(MASConstraintMaker *make) {
// 相對于self.view頂部的約束
make.top.equalTo(self.view).offset(100);
make.right.equalTo(self.view).offset(-20);
make.left.equalTo(@20);//方向上的約束默認相對于俯視圖
make.height.equalTo(@50);
}];
從給出的三種添加約束的代碼中我們可以看出:
系統方法添加約束的代碼量很多且相似代碼占據絕大部分
VFL語句自我感覺隱晦難懂且使用字符串的方式不符合我們平時編程習慣。
masonry采用鏈式編程的思想,使用點語法進行方法調用簡單且靈活。
接下來我們就看看如何使用masonry
二、Masonry的基本用法
首先masonry有兩個宏
MAS_SHORTHAND,只要#define這個宏,就不用帶mas_前綴了,但是不建議去掉,比如view.mas_left->view.left,這樣容易和工程中其他view的category產生沖突。
MAS_SHORTHAND_GLOBALS,只要#define這個宏,qualTo就等價于mas_equalTo,后面就可以直接使用基本類型,而不需要轉換成NSNumber了,建議添加。
- 添加約束
redView是距離父視圖上下左右的約束均為20,blueView是redView的姿勢圖,寬高的redView一半,并且居中。
orangeView、purpleView和greenView是blueView的子視圖,它們高度相等,左右間距均為10,上下間距均為15。
UIView *redView = [UIView new];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
UIEdgeInsets padding1 = UIEdgeInsetsMake(20, 20, 20, 20);
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
// 方法一
// make.right.equalTo( self.view.mas_right).offset(-20);
// make.left.equalTo(self.view).offset(20);//默認相對方向為你想要設置的方向
// make.top.equalTo(redView.superview).offset(20);
// make.bottom.equalTo(-20);//默認相對視圖為父視圖
// 方法二
// make.top.left.equalTo(20);// 可同時設置多個約束
// make.right.bottom.equalTo(-20);
// 方法三
// make.top.left.right.bottom.equalTo(self.view).insets(padding1);
// 方法四
make.edges.equalTo(padding1);//設置內邊距
}];
UIView *blueView = [UIView new];
blueView.backgroundColor = [UIColor blueColor];
[redView addSubview:blueView];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(redView);
make.size.equalTo(redView).multipliedBy(0.5);//父視圖寬度的一半
// 等價于make.size.equalTo(redView).dividedBy(2.0);
}];
UIView *orangeView = [UIView new];
orangeView.backgroundColor = [UIColor orangeColor];
[blueView addSubview:orangeView];
UIView *purpleView = [UIView new];
purpleView.backgroundColor = [UIColor purpleColor];
[blueView addSubview:purpleView];
UIView *greenView = [UIView new];
greenView.backgroundColor = [UIColor greenColor];
[blueView addSubview:greenView];
UIEdgeInsets padding2 = UIEdgeInsetsMake(15, 10, 15, 10);
[orangeView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(blueView).insets(padding2);
make.right.equalTo(purpleView.mas_left).insets(padding2);
//上面寫法等價于make.right.equalTo(purpleView.mas_left).offset(-10);
make.width.equalTo(purpleView.mas_width);
make.height.equalTo(@[purpleView,greenView]);//多個視圖同一類型約束相等可傳數組
make.bottom.equalTo(greenView.mas_top).insets(padding2);
}];
[purpleView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.right.equalTo(blueView).insets(padding2);
}];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(blueView).insets(padding2);
}];
- 更新約束、重置約束
更新約束:修改視圖已有約束,如果該視圖沒有該約束則會添加這個約束。
給上面的blueVIew添加一個手勢方法,當被點擊時blueView的寬度變成父視圖的0.8倍,如下代碼:
[blueView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(updateConstraints:)]];
-(void)updateConstraints:(UITapGestureRecognizer *)tap{
UIView *blueView = tap.view;
[blueView mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(blueView.superview).multipliedBy(0.8);
}];
}
重置約束:當我們想要通過更新現有約束無法滿足需求時,需要重新設置約束。
給原來的blueVIew重新添加一個手勢方法,當被點擊是blueView的位置距離底部和右邊為10,如下代碼:
[blueView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(remarkConstraints:)]];
- (void)remarkConstraints:(UITapGestureRecognizer *)tap{
UIView *blueView = tap.view;
[blueView mas_updateConstraints:^(MASConstraintMaker *make) {
make.right.bottom.equalTo(blueView.superview).offset(-10);
make.width.equalTo(blueView.superview).multipliedBy(0.5);
}];
}
- 動畫效果約束
masonry動畫效果很簡單,只需在動畫的block里view的父視圖調用layoutIfNeeded即可,但是在之前我們必須調用setNeedsUpdateConstraints和updateConstraintsIfNeeded告訴視圖需要更新約束。在上面代碼中添加如下代碼:
// 告訴約束需要更新,但不會立即更新,
[blueView.superview setNeedsUpdateConstraints];
// 檢測當前視圖及其子視圖是否需要更新約束
[blueView.superview updateConstraintsIfNeeded];
[UIView animateWithDuration:3 animations:^{
// 立即更新約束
[blueView.superview layoutIfNeeded];
}]
三、Masonry的使用技巧
- 多個控件相對于父控件居中
思路:首先讓父控件居中,再根據子控件的size和間距拉伸父控件。水平居中分布代碼如下:(垂直居中可自己嘗試)
UIView *supView = [UIView new];
supView.backgroundColor = [UIColor redColor];
[self.view addSubview:supView];
UIView *leftView = [UIView new];
leftView.backgroundColor = [UIColor orangeColor];
[supView addSubview:leftView];
UIView *rightView = [UIView new];
rightView.backgroundColor = [UIColor greenColor];
[supView addSubview:rightView];
[supView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);//設置父視圖居中
}];
[leftView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.equalTo(supView);//設置上左下對齊父視圖
make.size.equalTo(CGSizeMake(80, 40));//設置尺寸
make.right.equalTo(rightView.mas_left).offset(-10);//設置間距
}];
[rightView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.top.bottom.equalTo(supView);//設置右上下對齊父視圖 (配合leftView就可以確定superView的寬高了)
make.size.equalTo(leftView);
}];
-
等間距view布局(view的寬度也是相同的)
masonry給array擴展一些方法,可以對數組里的多個view同時設置約束,其中mas_distributeViewsAlongAxis就可以設置數組里view的間距問題,一種方法是指定間距拉伸view的寬度,另一種方法是指定view的寬度等分間距。
8CD42876-F21D-4BCD-AC4F-CD54BF989B44.png給出代碼及注釋如下:
- UIScrollView約束
思路:給scrollView添加唯一的子視圖contentView,通過拉伸子視圖的size來確定scrollView的contentSize,代碼和實現見下方代碼和注釋:
-(void)scrollViewTest{
UIScrollView *scrollView = [UIScrollView new];
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
UIView *contentView = [UIView new];
contentView.backgroundColor = [UIColor lightGrayColor];
[scrollView addSubview:contentView];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
//設置邊距相對于scrollView的約束
//(自己的見解:contentView的edges相對scrollView的edges的約束 contentView實際上被拉伸的寬高是相對于scrollView的contentSize的)
make.edges.equalTo(scrollView);
//因為上面的寬高是相對于contentSize的 所以為0 這里需要設置contentView的寬度約束后 scrollView的contentSize.width就會拉伸
make.width.equalTo(scrollView);
}];
NSMutableArray *lastArray;
NSMutableArray *array = [NSMutableArray array];
for (NSInteger i = 0; i<99; i++) {
UIView *view = [UIView new];
view.backgroundColor = [UIColor colorWithRed:(arc4random()%255)/255.0 green:(arc4random()%255)/255.0 blue:(arc4random()%255)/255.0 alpha:1];
[contentView addSubview:view];
[array addObject:view];
if ((i+1)%3 == 0) { //一行分三個 最后一行如果不足三個則忽略
[array mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:50 leadSpacing:20 tailSpacing:20];
[array mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(50);
make.top.equalTo(lastArray.count?((UIView *)lastArray[0]).mas_bottom:contentView).offset(10);
}];
lastArray = array.mutableCopy;
array = [NSMutableArray array];
}
}
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
// 這里設置contentView的底部約束等于最后一排視圖底部約束后 contentView的高度就確定了 scrollView的contentSize.height就會拉伸
make.bottom.equalTo(((UIView *)lastArray[0]).mas_bottom);
}];
}
四、UITableViewCell自適應高度
UITableView算是我們開發中使用頻率最多的控件了,但是cell的高度的計算確實一直是我們頭疼的問題。我們最理想的狀態:cell的高度自己決定,而不需要我們進行計算。下面就介紹autoLayout是如何實現的自適應高度的
- 系統方法。self-sizing cell,顧名思義,cell的高度自己決定,這正是我們想要的。
如果約束正確,所有contentView的子視圖的約束可以撐開contentVIew,那么只需要設置:
tableView.estimatedRowHeight = 100;//一個估算值
tableView.rowHeight = UITableViewAutomaticDimension;//可以不設置,ios8之后默認值
只適用于iOS8及以上,且在數據量超大的時候,進行插入和刪除,都是很不流暢的,并且在調用scrollToRowAtIndexPath:方法時,甚至會出現白屏情況。當然這種方法針對一些常用場景,比如新聞列表、商品列表什么的,數據量沒那么大且不涉及到新增、刪除數據的時候,這種方法,還是蠻不錯的,寫起來很簡便。
- UITableView+FDTemplateLayoutCell
這是一個三方框架,同樣簡單的api,就可以達到自適應cell高度的效果。作者通過RunLoop進行緩存、預緩存,使tableView的滑動十分流暢。如果想要了解更多的可以看作者博客,寫的十分詳盡。
!注意:使用這個框架時,cell的創建必須要以-registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注冊方法創建的。具體使用方法見代碼:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TestCell"];
[cell configCellWithImageStr:@"logo" contentStr:_dataArray[indexPath.row]];
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return [tableView fd_heightForCellWithIdentifier:@"TestCell" cacheByIndexPath:indexPath configuration:^(TestCell *cell) {
// 這里調用cell子視圖賦值的方法
[cell configCellWithImageStr:@"logo" contentStr:_dataArray[indexPath.row]];
}];
}
-(void)configCellWithImageStr:(NSString *)imageStr contentStr:(NSString *)content{
self.logoIv.image = [UIImage imageNamed:imageStr];
self.contentLb.text = content;
}