Masonry -- 使用純代碼進行iOS應用的autolayout自適應布局,對很多零散的進行整理
?簡介
簡化iOS應用使用純代碼機型自適應布局的工作,使用一種簡潔高效的語法替代NSLayoutConstraints.
項目主頁:?Masonry
最新示例:?點擊下載
項目簡議: 如果再看到關于純代碼,xib或storyboard,使用哪種方式進行UI布局更合適的討論,請推薦他們先試用下 Masonry. Masonry,像xib一樣快速,同時擁有作為純代碼方式的靈活性 -- github關注度 7800 + 是有原因的!
快速入門
安裝
使用 CocoaPods 安裝
```
pod 'Masonry'
```
推薦在你的在 prefix.pch 中引入頭文件:
```
// 定義這個常量,就可以在使用Masonry不必總帶著前綴 `mas_`:
#define MAS_SHORTHAND
// 定義這個常量,以支持在 Masonry 語法中自動將基本類型轉換為 object 類型:
#define MAS_SHORTHAND_GLOBALS
#import "Masonry.h"
```
使用
初始Masonry
這是使用MASConstraintMaker創建的約束:
```
/* 注意:view1應首先添加為某個視圖的子視圖,superview是一個局部變量,指view1的父視圖. */
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).offset(padding.top);
make.left.equalTo(superview.mas_left).offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).offset(-padding.bottom);
make.right.equalTo(superview.mas_right).offset(-padding.right);
}];
```
甚至可以更短:
```
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).insets(padding);
}];
```
## 不止可以表達相等關系
> `.equalTo` 等價于 NSLayoutRelationEqual
> `.lessThanOrEqualTo` 等價于 NSLayoutRelationLessThanOrEqual
> `.greaterThanOrEqualTo` 等價于 NSLayoutRelationGreaterThanOrEqual
這三個表達相等關系的語句,可以接受一個參數;此參數可以為以下任意一個:
1、MASViewAttribute
```
make.centerX.lessThanOrEqualTo(view2.mas_left);
```
| 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
如果你需要 view.left 大于或等于label.left:
```
// 下面兩個約束是完全等效的.
make.left.greaterThanOrEqualTo(label);
make.left.greaterThanOrEqualTo(label.mas_left);
```
3、NSNumber
自適應布局允許將寬度或高度設置為固定值.如果你想要給視圖一個最小或最大值,你可以這樣:
```
//width >= 200 && width <= 400
make.width.greaterThanOrEqualTo(@200);
make.width.lessThanOrEqualTo(@400)
```
但是自適應布局不支持將 left,right, centerY等設為固定值.如果你給這些屬性傳遞一個常量, Masonry會自動將它們轉換為相對于其父視圖的相對值:
```
//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));
```
4、NSArry
一個數組,里面可以混合是前述三種類型的任意幾種:
```
// 表達三個視圖等高的約束.
make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1, @100, view3.right]);
```
#### 兩個特殊的等比方法
* 第一種

```
//在紅色View里面放三個正方形View, 等間距為10
NSInteger padding = 10;
UIView *yellowView1 = [[UIView alloc] init];
yellowView1.backgroundColor = [UIColor yellowColor];
[redView addSubview:yellowView1];
UIView *yellowView2 = [[UIView alloc] init];
yellowView2.backgroundColor = [UIColor yellowColor];
[redView addSubview:yellowView2];
UIView *yellowView3 = [[UIView alloc] init];
yellowView3.backgroundColor = [UIColor yellowColor];
[redView addSubview:yellowView3];
[@[yellowView1, yellowView2, yellowView3] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:padding leadSpacing:padding tailSpacing:padding];
[@[yellowView1, yellowView2, yellowView3] mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(redView).offset(10);
make.height.mas_equalTo(yellowView3.mas_width);
}];
```
```
/**
*? 確定間距等間距布局
*
*? @param axisType? ? 布局方向
*? @param fixedSpacing 兩個item之間的間距(最左面的item和左邊, 最右邊item和右邊都不是這個)
*? @param leadSpacing? 第一個item到父視圖邊距
*? @param tailSpacing? 最后一個item到父視圖邊距
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
```
所以也就知道了, 將`fixedSpacing`, `leadSpacing`, `tailSpacing`都賦值同一個間距, 數組內的的View就會自動計算出寬度, 完成水平方向的布局.
要注意的是, 這個方法僅僅完成了水平方向的布局, 如果想確定這幾個View的位置, 還需要指定豎直方向位置和高度, 這里可以用數組直接調用 `mas_makeConstraints:^(MASConstraintMaker *make){}`完成布局.
* 第二種

```
/**
*? distribute with fixed item size
*
*? @param axisType? 布局方向
*? @param fixedItemLength 每個item的布局方向的長度
*? @param leadSpacing? 第一個item到父視圖邊距
*? @param tailSpacing? 最后一個item到父視圖邊距
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
```
區別就是這里除了布局方向, 第一個和最后一個View的邊距, 這里需要指定的是每個item的長度, 自動計算間隙, 所以這個要實現等間距, 其實是要通過item的數量, 以及父視圖的寬度先計算出間距, 然后賦值給, leadSpacing和tailSpacing, 比如`CGFloat padding2 = (300 - 3 * 30) / 4;` 這里的300就是父視圖的寬度, 30是指定的每個item的寬度, 這樣計算好就可以保證, leadSpacing, tailSpacing, 和item之間的間距相同, 實現布局.
同樣這個方法完成了水平方向的布局, 還需要完成豎直方向的布局.
* 第三種

```
//在紅色View里面放三個大小不一樣的綠色正方形, 間隙等大, masonry并沒提供相關方法
NSMutableArray *greenViews = [NSMutableArray array];
for (NSInteger i = 0; i < 3; i++) {
UIView *greenView = [[UIView alloc] init];
greenView.backgroundColor = [UIColor greenColor];
[redView addSubview:greenView];
[greenViews addObject:greenView];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(redView).offset(-10);
make.width.mas_equalTo(i*20 + 20);
make.height.mas_equalTo(greenView.mas_width);
}];
}
[redView distributeSpacingHorizontallyWith:greenViews];
```
首先在for循環內 , 完成了底部位置, 寬, 高的布局, 還缺少水平方向的位置, 即還要確定每個view的X, 這里用到了一個UIView的分類
`- (void) distributeSpacingHorizontallyWith:(NSArray*)views;`
這個分類直接用的里脊串的一篇文章中的代碼, 就不貼出代碼了, 簡單說一下原理, 如圖所示:

實現原理就是在View中創建greenViews.count + 1個占位的View(藍色), 之后通過布局, 使占位View與要布局的View依次排開, 左右間距為0, 同時要約束所有的占位View寬度相等, 這樣看來, 這些占位View的寬度, 就是greenViews的間距, 也就可以實現等間距布局了.
## 約束的優先級
> `.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);
```
## 等比例自適應
> `.multipliedBy` 允許你指定一個兩個視圖的某個屬性等比例變化
>
> `item1.attribute1 = multiplier × item2.attribute2 + constant`,此為約束的計算公式, `.multipliedBy`本質上是用來限定 `multiplier`的
> 注意,因為編程中的坐標系從父視圖左上頂點開始,所以指定基于父視圖的left或者top的multiplier是沒有意義的,因為父視圖的left和top總為0.
> 如果你需要一個視圖隨著父視圖的寬度和高度,位置自動變化,你應該同時指定 right,bottom,width,height與父視圖對應屬性的比例(基于某個尺寸下的相對位置計算出的比例),并且constant必須為0.
```
// 指定寬度為父視圖的 1/4.
make.width.equalTo(superview).multipliedBy(0.25);
```
## 工具方法
Masonry提供了一些工具方法來進一步簡化約束的創建.
### edges 邊界
```
//使 top, left, bottom, right等于 view2
make.edges.equalTo(view2);
//使 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 尺寸
```
// 使寬度和高度大于或等于 titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
//使 width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
```
### center 中心
```
//使 centerX和 centerY = button1
make.center.equalTo(button1)
//使 centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
```
你可以使用鏈式語法來增強代碼可讀性:
```
// 除top外,其他約束都與父視圖相等.
make.left.right.bottom.equalTo(superview);
make.top.equalTo(otherView);
```
## 更新約束
有時,你需要修改已經存在的約束來實現動畫效果或者移除/替換已有約束.在 Masonry 中,有幾種不同的更新視圖約束的途徑:
#### 1、References 引用
你可以把 Masonry 語法返回的約束或約束數組,存儲到一個局部變量或者類的屬性中,以供后續操作某個約束.
```
// 聲明屬性
@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);
}];
...
// 然后你就可以操作這個屬性.
[self.topConstraint uninstall];
```
#### 2、mas_updateConstraints
如果你只是想添加新的約束,你可以使用便利方法mas_updateConstraints,不需要使用 mas_makeConstraints. mas_updateConstraints,不會移除已經存在的約束(即使新舊約束間相互沖突).
```
// 重寫視圖的updateConstraints方法: 這是Apple推薦的添加/更新約束的位置.
// 這個方法可以被多次調用以響應setNeedsUpdateConstraints方法.
// 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);
}];
//根據apple機制,最后應調用父類的updateConstraints方法.
[super updateConstraints];
}
```
#### 3. mas_remakeConstraints
`mas_remakeConstraints`與`mas_updateConstraints`相似,不同之處在于: `mas_remakeConstraints` 會先移除視圖上已有的約束,再去創建新的約束.
```
- (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);
}
}];
}
```