Masonry使用總結
一、Masonry簡介
Masonry是一個輕量級的布局框架,適用于
iOS
以及OS X
。它用簡潔的語法對官方的AutoLayout
進行了封裝。 Masonry有它自己的一套框架用來描述NSLayoutConstraints
布局的DSL
,提高了約束代碼的簡潔性與可讀性。
Masonry現處于bugfix only
狀態,將不再有功能性的更新。會有更多的開發者加入Swift
陣營,推薦使用Swift寫的Snapkit
框架來布局。
二、導入Masonry框架
- 使用
Cocoapods
來導入框架,在使用到該框架的文件中添加主頭文件:#import <Masonry/Masonry.h>
。
- 可以參考這篇文章來配置使用
Cocoapods
-- > Cocoapods的安裝和使用
- 使用直接拖拽的方式拉入框架文件夾,在使用到該框架的文件中添加主頭文件:
#import "Masonry.h"
。
三、Masonry的特性
MASViewAttribute
|MASViewAttribute |NSLayoutAttribute
|:----: |:----:
|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簡化前綴的宏定義
- 為了增加代碼的可讀性這里有兩個簡化代碼的宏:
#define MAS_SHORTHAND
和#define MAS_SHORTHAND_GLOBALS
-
MAS_SHORTHAND
:只要在導入Masonry
主頭文件之前定義這個宏, 那么以后在使用Masonry
框架中的屬性和方法的時候, 就可以省略mas_
前綴 -
MAS_SHORTHAND_GLOBALS
:只要在導入Masonry
主頭文件之前定義這個宏,那么就可以讓equalTo
函數接收基本數據類型, 內部會對基本數據類型進行包裝
注意:這兩個宏如果想有效使用,必須要在添加Masonry
頭文件之前導入進去。在沒有增加宏`MAS_SHORTHAND_GLOBALS時,下面這句是會報錯的。
make.top.equalTo(42);--> make.top.equalTo([NSNumber numberWithInt:42]);
四、Masonry約束添加步驟
- 自定義
UIView
。 - 將自定義的
UIView
添加到父視圖上。 - 添加約束
五、Masonry
的具體使用
對一個控件添加約束條件要最終滿足可以確定這個空間的大小和位置,否則會報錯缺少約束,當然也要避免約束沖突。下面對
View1
的左右上下(位置)都進行約束,并沒有對其大小進行約束,但是可根據上述約束自動設置View1
的大小。
1. 創建一個View,左右上下空出10個像素
UIView *view1 = [[UIView alloc]init];
view1.backgroundColor = [UIColor greenColor];
[self.view addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(150, 30, 30, 30);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).with.offset(padding.top);
make.left.equalTo(self.view.mas_left).with.offset(padding.left);
make.bottom.equalTo(self.view.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(self.view.mas_right).with.offset(-padding.right);
}];
// 一句代碼代替上面的多行
// [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
// make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(150, 30, 30, 30));
// }];
2. 使用Priority
屬性來做簡單的動畫
.priority
allows you to specify an exact priority
.priorityHigh
equivalent to UILayoutPriorityDefaultHigh
.priorityMedium
is half way between high and low
.priorityLow
equivalent to UILayoutPriorityDefaultLow
- 優先級約束一般放在一個控件約束的最后面,下面看示例。
// 紅色View
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
// 藍色View
self.blueView = [[UIView alloc]init];
self.blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.blueView];
// 黃色View
UIView *yellowView = [[UIView alloc]init];
yellowView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:yellowView];
// ---紅色View--- 添加約束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.view.mas_left).with.offset(20);
make.bottom.mas_equalTo(self.view.mas_bottom).with.offset(-80);
make.height.equalTo([NSNumber numberWithInt:50]);
}];
// ---藍色View--- 添加約束
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(redView.mas_right).with.offset(40);
make.bottom.width.height.mas_equalTo(redView);
}];
// ---黃色View--- 添加約束
[yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.blueView.mas_right).with.offset(40);
make.right.mas_equalTo(self.view.mas_right).with.offset(-20);
make.bottom.width.height.mas_equalTo(redView);
// 優先級設置為250,最高1000(默認)
make.left.mas_equalTo(redView.mas_right).with.offset(20).priority(250);
}];
// 點擊屏幕移除藍色View
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.blueView removeFromSuperview];
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
}
解:這里的三個View的寬度是根據約束自動推斷設置的,對黃色的View設置了一個與紅色View有關的
priority(250)
的優先級,它同時有對藍色View有個最高的優先級約束(make.left.mas_equalTo(self.blueView.mas_right).with.offset(40);
)。當點擊屏幕是,我將藍色View移除,此時第二優先級就是生效。
3. Masonry官方Demo之Basic
- (void)testBasic
{
int padding = 15;
UIView *greenView = [[UIView alloc]init];
[self.view addSubview:greenView];
greenView.backgroundColor = [UIColor greenColor];
UIView *redView = [[UIView alloc]init];
[self.view addSubview:redView];
redView.backgroundColor = [UIColor redColor];
UIView *blueView = [[UIView alloc]init];
[self.view addSubview:blueView];
blueView.backgroundColor = [UIColor blueColor];
// 對 綠色View 進行約束
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.view.mas_left).with.offset(padding); // X
make.top.mas_equalTo(self.view.mas_top).with.offset(padding); // Y
make.bottom.mas_equalTo(blueView.mas_top).with.offset(-padding);// Y --> 推斷出 Height
make.width.mas_equalTo(redView); // Width == 紅色View(它推斷出Width)
}];
// 對 紅色View 進行約束
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(greenView.mas_right).with.offset(padding); // X
make.right.mas_equalTo(self.view.mas_right).with.offset(-padding);// X --> 推斷出 Width
make.bottom.and.height.mas_equalTo(greenView); // Y & Height == 綠色View(它推斷出 Height&Y)
}];
// 對 藍色View 進行約束
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.view.mas_left).with.offset(padding); // X
make.right.mas_equalTo(self.view.mas_right).with.offset(-padding); // X --> 推斷出 Width
make.bottom.mas_equalTo(self.view.mas_bottom).with.offset(-padding); // Y
make.height.mas_equalTo(greenView); // 注意1:Height == 綠色View(它推斷出Height)
}];
}
總結:對
綠色View
約束了距離左邊
的距離(即確定了X值
),約束了距離頂部
和底部藍色View
的距離(這里有個坑需要注意,很多人會認為約束了這兩個就能推斷出Height
,其實是錯誤的。除非藍色View
添加一個約束讓它們倆高度相同
,否則即便知道了它們倆間距
和分別對頂部
和底部
的距離,也不知道紅色View
和藍色View
高度如何。)
口訣:誰推斷出大小位置(一或多),與其有約束關系的View的大小位置(一或多)向它看齊。
4. Masonry的更新約束 mas_updateConstraints
Alternatively if you are only updating the constant value of the constraint you can use the convience method
mas_updateConstraints
instead ofmas_makeConstraints
創建一個按鈕,約束好它的位置(居中,寬高等于100且小于屏幕寬高值)。每次點擊一次這個按鈕,其寬高將增大一定的倍數,最終其寬高等于屏幕寬高時將不再變化。
@interface ViewController ()
@property (nonatomic, strong) UIButton *growingButton;
@property (nonatomic, assign) CGFloat scacle;
@end
- (void)createGrowingButton {
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"點我放大" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = 3;
[self.growingButton addTarget:self action:@selector(onGrowButtonTaped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.growingButton];
self.scacle = 1.0;
[self.growingButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
// 初始寬、高為100,優先級最低
make.width.height.mas_equalTo(100 * self.scacle);
// 最大放大到整個view
make.width.height.lessThanOrEqualTo(self.view);
}];
}
- (void)onGrowButtonTaped:(UIButton *)sender {
self.scacle += 1.0;
// 告訴self.view約束需要更新
[self.view setNeedsUpdateConstraints];
// 調用此方法告訴self.view檢測是否需要更新約束,若需要則更新,下面添加動畫效果才起作用
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
#pragma mark - updateViewConstraints
// 重寫該方法來更新約束
- (void)updateViewConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
// 這里寫需要更新的約束,不用更新的約束將繼續存在
// 不會被取代,如:其寬高小于屏幕寬高不需要重新再約束
make.width.height.mas_equalTo(100 * self.scacle);
}];
[super updateViewConstraints];
}
5. Masonry的重寫約束 mas_remakeConstraints
Creates a MASConstraintMaker with the callee view. Any constraints defined are added to the view or the appropriate superview once the block has finished executing. All constraints previously installed for the view will be removed.
創建一個按鈕,約束好其位置(與屏幕上左右的距離為0,與屏幕底部距離為350),點擊按鈕后全屏展現(即與屏幕底部距離為0)。
@property (nonatomic, strong) UIButton *growingButton;
@property (nonatomic, assign) BOOL isExpanded;
- (void)createExpandButton {
self.isExpanded = NO;
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"點我展開" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = 3;
self.growingButton.backgroundColor = [UIColor redColor];
[self.growingButton addTarget:self action:@selector(onGrowButtonTaped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.growingButton];
[self.growingButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.right.mas_equalTo(0);
make.bottom.mas_equalTo(-350);
}];
}
#pragma mark - updateViewConstraints
- (void)updateViewConstraints {
// 這里使用update也能實現效果
// remake會將之前的全部移除,然后重新添加
__weak __typeof(self) weakSelf = self;
[self.growingButton mas_remakeConstraints:^(MASConstraintMaker *make) {
// 這里重寫全部約束,之前的約束都將失效
make.top.mas_equalTo(0);
make.left.right.mas_equalTo(0);
if (weakSelf.isExpanded) {
make.bottom.mas_equalTo(0);
} else {
make.bottom.mas_equalTo(-350);
}
}];
[super updateViewConstraints];
}
- (void)onGrowButtonTaped:(UIButton *)sender {
self.isExpanded = !self.isExpanded;
if (!self.isExpanded) {
[self.growingButton setTitle:@"點我展開" forState:UIControlStateNormal];
} else {
[self.growingButton setTitle:@"點我收起" forState:UIControlStateNormal];
}
// 告訴self.view約束需要更新
[self.view setNeedsUpdateConstraints];
// 調用此方法告訴self.view檢測是否需要更新約束,若需要則更新,下面添加動畫效果才起作用
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
-
mas_remakeConstraints
和mas_updateConstraints
的區別在于前者重新對視圖進行了約束(拋棄了之前的約束),后者是更新約束條件(保留未更新的約束,如:這次更新了對height
的約束,其他對X&Y以及寬的約束不變)。
6. Masonry的比例使用 multipliedBy
使用multipliedBy必須是對同一個控件本身,如果修改成相對于其它控件會出導致Crash。
UIView *topView = [[UIView alloc]init];
[topView setBackgroundColor:[UIColor redColor]];
[self.view addSubview:topView];
UIView *topInnerView = [[UIView alloc]init];
[topInnerView setBackgroundColor:[UIColor greenColor]];
[topView addSubview:topInnerView];
UIView *bottomView = [[UIView alloc]init];
[bottomView setBackgroundColor:[UIColor orangeColor]];
[self.view addSubview:bottomView];
UIView *bottomInnerView = [[UIView alloc]init];
[bottomInnerView setBackgroundColor:[UIColor blueColor]];
[bottomView addSubview:bottomInnerView];
[topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_equalTo(0);
make.height.mas_equalTo(bottomView);
}];
[topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.width.mas_equalTo(topInnerView.mas_height).multipliedBy(3);
make.center.mas_equalTo(topView);
}];
[bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.bottom.right.mas_equalTo(0);
make.height.mas_equalTo(topView);
make.top.mas_equalTo(topView.mas_bottom);
}];
[bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(bottomView);
make.height.mas_equalTo(bottomInnerView.mas_width).multipliedBy(3);
make.center.mas_equalTo(bottomView);
}];