接上篇,本篇继续对Masonry 进行学习,接上篇示例:

(6)Masonry 布局实现iOS 计算器

 - (void)exp4 {
WS(weakSelf);
// 申明区域,displayView 是显示布局,keyboardView 是键盘区域
UIView *displayView = [UIView new]; // 创建并添加 显示区域
[displayView setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:displayView]; UIView *keyboardView = [UIView new];
[self.view addSubview:keyboardView]; // 添加键盘区域
// 先按1:3分割 displayView (显示结果区域)和keyboardView (键盘区域)
[displayView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(weakSelf.view.mas_top);
make.left.and.right.equalTo(weakSelf.view); // 顶部和左右 和self.view 相等
make.height.equalTo(keyboardView).multipliedBy(0.3f); // displayView 的高度是 keyboardView 的高度乘以 0.3 keyboardView 的高度是displayView 高度的三倍
}]; [keyboardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(displayView.mas_bottom); // 键盘区域的top 紧接着显示结果区域的bottom
make.bottom.equalTo(weakSelf.view.mas_bottom); // 键盘区域的底部等于self.view 的底部
make.left.and.right.equalTo(weakSelf.view); // 左右相等
}]; // 设置显示位置的数字为0
UILabel *displayNum = [[UILabel alloc] init];
[displayView addSubview:displayNum];
displayNum.text = @"";
displayNum.font = [UIFont fontWithName:@"HeiTi SC" size:]; // 设置字体 @"HeiTi SC"
displayNum.textColor = [UIColor whiteColor];
displayNum.textAlignment = NSTextAlignmentRight;
[displayNum mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.and.right.equalTo(displayView).with.offset(-); // 左和右各缩进 10
make.bottom.equalTo(displayView).with.offset(-); // 底部也缩进 10 // label 的高度能根据label 的内容自动适应高度 只要确定一个底部或者顶部就能确定label 的约束 label 的宽度要确定才能确定高度
}];
// 定义键盘键名称,? 号代表合并的单元格
NSArray *keys = @[@"AC", @"+/-", @"%", @"÷"
, @"", @"", @"", @"x"
, @"", @"", @"", @"-"
, @"", @"", @"", @"+"
, @"", @"?", @".", @"="];
int indexOfKeys = ;
for (NSString *key in keys) {
// 循环所有键
indexOfKeys++;
int rowNum = indexOfKeys % == ? indexOfKeys / : indexOfKeys / + ; // 求各个key 的行和列 注意是从 1 开始的 求行的时候, 先对 4 取余数, 如果是 0 的话正好是 4 的倍数 ,那就是最后一列的key indexOfKeys 这个时候是 4 或 8 或 12 或 16 或 20 , 这个时候取 indexOfKeys 的商正好是key 所在的行 ,其他的同理
int colNum = indexOfKeys % == ? : indexOfKeys % ;
NSLog(@"index is: %d and row: %d, col: %d", indexOfKeys, rowNum, colNum);
// 键样式
UIButton *keyView = [UIButton buttonWithType:UIButtonTypeCustom];
[keyboardView addSubview:keyView];
[keyView setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[keyView setTitle:key forState:UIControlStateNormal];
[keyView.layer setBorderWidth:];
[keyView.layer setBorderColor:[UIColor blackColor].CGColor];
[keyView.titleLabel setFont:[UIFont fontWithName:@"Arial-BoldItalicMT" size:]]; // 设置字体 @"Arial-BoldItalicMT"
// 键约束
[keyView mas_makeConstraints:^(MASConstraintMaker *make) {
// 处理 0 合并单元格
if ([key isEqualToString:@""] || [key isEqualToString:@"?"]) {
if ([key isEqualToString:@""]) {
[keyView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(keyboardView.mas_height).with.multipliedBy(.2f); // 高度是keyboardView 的高度的五分之一
make.width.equalTo(keyboardView.mas_width).multipliedBy(.); // 宽度是keyboardView 的宽度的二分之一
make.left.equalTo(keyboardView.mas_left); // 左边相等
make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.9f); // 这个baseline 没有看太懂是什么意思
}];
} if ([key isEqualToString:@"?"]) { // 如果是问号的话直接被移除了
[keyView removeFromSuperview];
}
}
// 正常的单元格
else {
make.width.equalTo(keyboardView.mas_width).with.multipliedBy(.25f); // 正常的单元格的话 宽度是 keyboardView 的宽度的四分之一
make.height.equalTo(keyboardView.mas_height).with.multipliedBy(.2f); // 高度是keyboardView 的高度的五分之一
// 按照行和列添加约束,这里添加行约束
switch (rowNum) {
case :
{
make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.1f);
keyView.backgroundColor = [UIColor colorWithRed: green: blue: alpha:];
}
break;
case :
{
make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.3f);
}
break;
case :
{
make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.5f);
}
break;
case :
{
make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.7f);
}
break;
case :
{
make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.9f);
}
break;
default:
break;
}
switch (colNum) {
case :
{
make.left.equalTo(keyboardView.mas_left);
}
break;
case :
{
make.right.equalTo(keyboardView.mas_centerX);
}
break;
case :
{
make.left.equalTo(keyboardView.mas_centerX);
}
break;
case :
{
make.right.equalTo(keyboardView.mas_right);
[keyView setBackgroundColor:[UIColor colorWithRed: green: blue: alpha:]];
}
break;
default:
break;
}
}
}];
}
}

(7)基础设计页面

 - (void)HYB {
UIView *greenView = UIView.new;
greenView.backgroundColor = UIColor.greenColor;
greenView.layer.borderColor = UIColor.blackColor.CGColor;
greenView.layer.borderWidth = ;
[self.view addSubview:greenView]; UIView *redView = UIView.new;
redView.backgroundColor = UIColor.redColor;
redView.layer.borderColor = UIColor.blackColor.CGColor;
redView.layer.borderWidth = ;
[self.view addSubview:redView]; UIView *blueView = UIView.new;
blueView.backgroundColor = UIColor.blueColor;
blueView.layer.borderColor = UIColor.blackColor.CGColor;
blueView.layer.borderWidth = ;
[self.view addSubview:blueView]; // 使三个控件等高
CGFloat padding = ;
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(padding);
make.left.mas_equalTo(padding);
make.right.mas_equalTo(redView.mas_left).offset(-padding);
make.bottom.mas_equalTo(blueView.mas_top).offset(-padding);
// 三个控件等高
make.height.mas_equalTo(@[redView, blueView]);
// 红绿这两个控件等宽
make.width.mas_equalTo(redView);
}]; [redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.height.bottom.mas_equalTo(greenView);
make.right.mas_equalTo(-padding);
make.left.mas_equalTo(greenView.mas_right).offset(padding);
}]; [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(greenView);
make.bottom.mas_equalTo(-padding);
make.left.mas_equalTo(padding);
make.right.mas_equalTo(-padding);
}];
}

约束使用链式方式,使用方法很简单,看起来像一句话。

看这句话:make.top.height.bottom.mas_equalTo(greenView),意思是:使我的顶部、高度和底部都与greenView的顶部、高度和底部相等。因此,只要greenView的约束添加好了,那么redView的顶部、高度和底部也就自动计算出来了。

大多时候,我们并不会将一句话完整地写出来,而是使用简写的方式。

如:make.right.mas_equalTo(-padding);

其完整的写法为:

make.right.mas_equalTo(bludView.superView.mas_right).offset(-padding);

当我们是要与父控件相对约束时,可以省略掉父视图。注意,并不是什么时候都可以省略,只有约束是同样的才可以省略。比如,约束都是right才可以。如果是一个left一个是right,那么就不能省略了。当然如果是两个不同的控件也是不能省略的。

(8)动画的形式更新约束(Masonry 以动画的形式更新约束,初始一个很小的按钮,点击就不断放大,最大就放大到全屏幕)

 #import "ViewController.h"
#import "Masonry.h" #define WS(weakSelf) __weak __typeof(&*self) weakSelf = self; @interface ViewController () @property(nonatomic, strong) UIButton *growingButton;
@property(nonatomic, assign) CGFloat scacle; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// 动画更新约束
WS(weakSelf);
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"点我放大" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = ;
[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(weakSelf.view);
// 初始宽、高为100,优先级最低
make.width.height.mas_equalTo( * weakSelf.scacle).priorityLow();
// 最大放大到整个View
make.width.height.lessThanOrEqualTo(weakSelf.view);
}];
} #pragma mark - updateViewConstraints
- (void)updateViewConstraints {
WS(weakSelf);
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(weakSelf.view);
// 初始宽高为100 优先级最低
make.width.height.mas_equalTo( * weakSelf.scacle).priorityLow();
// 最大放大到整个View
make.width.height.lessThanOrEqualTo(weakSelf.view);
}]; [super updateViewConstraints];
} - (void)onGrowButtonTaped:(UIButton *)sender {
WS(weakSelf);
self.scacle += 0.5; // 一次增加 0.5 倍
// 告诉self.view 约束需要更新
[self.view setNeedsUpdateConstraints];
// 调用此方法告诉self.view 检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
[self.view updateConstraintsIfNeeded]; [UIView animateWithDuration:0.3 animations:^{
[weakSelf.view layoutIfNeeded];
}];
}

[self.growingButton mas_makeConstraints:^(MASConstraintMaker *make) {

make.center.mas_equalTo(weakSelf.view);

// 初始宽、高为100,优先级最低

make.width.height.mas_equalTo(100 * weakSelf.scacle).priorityLow(); // 让控件的宽和高相等且优先级最低。关于优先级,后面再讲

// 最大放大到整个View

make.width.height.lessThanOrEqualTo(weakSelf.view); // 让控件的宽和高小于或者等于self.view 的宽和高, 因此这个控件的最多能放大到全屏幕

}];

将这三行代码放在一起,形成的话就是:使控件与父视图始终保持居中,控件的宽和高最大不能超过屏幕,且控件的宽和高可以变化。由于我们设置了第二行的代码优先级为priorityLow,因此其优先级是最低的,所以就可以保证宽高不能超过屏幕。 其实,当我们将更新代码放到updateViewConstraints这个方法中时,这些约束就不需要了,这里写上去的目的只是想说明用于不用都没有关系。

  // 告诉self.view 约束需要更新

看下面的方法,就是动画更新约束的核心代码:

[self.view setNeedsUpdateConstraints];

  // 想要更新约束时添加动画,就需要调用关键的一行代码:setNeedsUpdateConstraints,这是选择对应的视图中的约束需要更新

  // 调用此方法告诉self.view 检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用

[self.view updateConstraintsIfNeeded];

  // 对于updateConstraintsIfNeeded这个方法并不是必须的,但是有时候不调用就无法起到我们的效果。但是,官方都是这么写的,从约束的更新原理上讲,这应该写上。我们要使约束立即生效,就必须调用layoutIfNeeded此方法,[UIView animateWithDuration:0.3 animations:^{ [self.view layoutIfNeeded]; }];

(9)移除约束

 #import "RemakeContraintsViewController.h"
#import "Masonry.h" @interface RemakeContraintsViewController () @property(nonatomic, strong) UIButton *growingButton;
@property(nonatomic, assign) BOOL isExpanded; @end @implementation RemakeContraintsViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor]; self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"点我展开" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = ;
self.growingButton.backgroundColor = [UIColor redColor];
[self.growingButton addTarget: self action:@selector(onGrowButtonTaped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.growingButton];
self.isExpanded = NO;
} - (void)updateViewConstraints {
// 这里使用update 也是一样的
// remake 会将之前的全部移除, 然后重新添加
[self.growingButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo();
make.left.right.mas_equalTo();
if (self.isExpanded) {
make.bottom.mas_equalTo();
} else {
make.bottom.mas_equalTo(-);
}
}]; [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];
}];
}

(10)整体动画更新约束

 #import "TotalUpdateViewController.h"
#import "Masonry.h" @interface TotalUpdateViewController () @property(nonatomic, strong) UIView *purpleView;
@property(nonatomic, strong) UIView *orangeView;
@property(nonatomic, assign) BOOL isExpaned; @end @implementation TotalUpdateViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor]; UIView *purpleView = [[UIView alloc] init];
purpleView.backgroundColor = UIColor.purpleColor;
purpleView.layer.borderColor = UIColor.blackColor.CGColor;
purpleView.layer.borderWidth = ;
[self.view addSubview:purpleView]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
[purpleView addGestureRecognizer:tap];
self.purpleView = purpleView; UIView *orangeView = UIView.new;
orangeView.backgroundColor = UIColor.orangeColor;
orangeView.layer.borderColor = UIColor.blackColor.CGColor;
orangeView.layer.borderWidth = ;
[self.view addSubview:orangeView];
self.orangeView = orangeView; // 这里,我们不使用updateViewConstraints 方法,但是我们一样可以做到
// 不过苹果推荐在updateViewConstraints 方法中更新约束或者添加约束
[self updateWithExpand:NO animated:NO]; UILabel *label = [[UILabel alloc] init];
label.numberOfLines = ;
label.textColor = [UIColor redColor];
label.font = [UIFont systemFontOfSize:];
label.textAlignment = NSTextAlignmentCenter;
label.text = @"点击purple 部分放大,orange 部分最大值 250 ,最小值 90 ";
[self.purpleView addSubview:label]; [label mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(); // 0 就是和底部相等
make.left.right.mas_equalTo();
}];
} - (void)updateWithExpand:(BOOL)isExpanded animated:(BOOL)animated {
self.isExpaned = isExpanded; [self.purpleView mas_updateConstraints:^(MASConstraintMaker *make) { // 更新紫色的约束
make.left.top.mas_equalTo();
make.right.mas_equalTo(-); // 右边和底部 0 是相等 小于0 的是缩进 大于的0 的是伸展 而左边和顶部正好相反
if (isExpanded) {
make.bottom.mas_equalTo(-);
} else {
make.bottom.mas_equalTo(-);
}
}]; [self.orangeView mas_updateConstraints:^(MASConstraintMaker *make) { // 更新橙色的约束
make.center.mas_equalTo(self.purpleView);
// 这里使用优先级处理
// 设置其最大值为 250 ,最小值为 90
if (!isExpanded) {
make.width.height.mas_equalTo( * 0.5).priorityLow();
} else {
make.width.height.mas_equalTo( * ).priorityLow();
} // 最大值为 250
make.width.height.lessThanOrEqualTo(@);
// 最小值为90
make.width.height.greaterThanOrEqualTo(@);
}]; if (animated) {
[self.view setNeedsUpdateConstraints];
[self.view updateConstraintsIfNeeded]; [UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
}
} - (void)onTap {
[self updateWithExpand:!self.isExpaned animated:YES];
}

展开与收起的关键代码在这里.设置最大最小值,这样就不会超出我们预期的范围。

// 最大值为250

make.width.height.lessThanOrEqualTo(@250);

// 最小值为90

make.width.height.greaterThanOrEqualTo(@90);

我们设置其固定的宽高,并且设置其优先级为最低,以保证我们所设置的最大最小值始终生效。

想要更新约束时添加动画,就需要调用关键的一行代码:setNeedsUpdateConstraints,这是选择对应的视图中的约束需要更新。

对于updateConstraintsIfNeeded这个方法并不是必须的,但是有时候不调用就无法起到我们的效果。但是,官方都是这么写的,从约束的更新原理上讲,这应该写上。我们要使约束立即生效,就必须调用layoutIfNeeded此方法。下面的方法,就是动画更新约束的核心代码:

     if (animated) {
// 告诉self.view 约束需要更新
[self.view setNeedsUpdateConstraints];
// 调用此方法告诉self.view 检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
[self.view updateConstraintsIfNeeded]; [UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
}

(11)比例(multipliedBy)

 // 首先创建4 个的View 视图 顶部和底部的View 平分屏幕
// 顶部View 红色
UIView *topView = [UIView new];
topView.backgroundColor = [UIColor redColor];
[self.view addSubview:topView];
// 顶部内部的View 绿色
UIView *topInnerView = [UIView new];
topInnerView.backgroundColor = [UIColor greenColor];
[topView addSubview:topInnerView];
// 底部View
UIView *bottomView = [UIView new];
bottomView.backgroundColor = [UIColor blackColor];
[self.view addSubview:bottomView];
// 底部内部的View 蓝色
UIView *bottomInnerView = [UIView new];
bottomInnerView.backgroundColor = [UIColor blueColor];
[bottomView addSubview:bottomInnerView]; [topView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(); // 上左右 等于 0
make.height.mas_equalTo(bottomView); // 高度两者相等
}]; // width = height / 3
[topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(topView); // 左右相等
make.width.mas_equalTo(topInnerView.mas_height).multipliedBy(); // topInnerView 的宽度等于高度乘以3
make.center.mas_equalTo(topView); // 中心重合 // 设置优先级
make.width.height.mas_equalTo(topView).priorityLow();
make.width.height.lessThanOrEqualTo(topView); // 宽度和高度最大也比topView 小于等于
}]; [bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.mas_equalTo(); // 左右底等于0
make.height.mas_equalTo(topView); // 高度和topView 相等
make.top.mas_equalTo(topView.mas_bottom); // top 等于 topView 的bottom
}]; // width / height / 比为 1 / 3.0 要求是同一个控件的属性比例
[bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(bottomView); // 顶部和底部等于bottomView 的顶部和底部
make.center.mas_equalTo(bottomView); // 中心相等
// 注意,这个multipliedBy 的使用只能是设置同一个控件,比如这里的bottonInnerView
// 设置高 / 宽 为 3:1
make.height.mas_equalTo(bottomInnerView.mas_width).multipliedBy(); // 高度等于宽度乘以 3 make.width.height.mas_equalTo(bottomView).priorityLow(); // 宽和高设置优先级
make.width.height.lessThanOrEqualTo(bottomView); // 宽度和高度最高也小于bottomView 的高度和宽度
}];

     [topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(topView); // 左右相等
make.width.mas_equalTo(topInnerView.mas_height).multipliedBy(); // topInnerView 的宽度等于高度乘以3
make.center.mas_equalTo(topView); // 中心重合 // 设置优先级
make.width.height.mas_equalTo(topView).priorityLow();
make.width.height.lessThanOrEqualTo(topView); // 宽度和高度最大也比topView 小于等于
}];

提示:使用multipliedBy必须是对同一个控件本身,比如,上面的代码中,我们都是对bottomInnerView.mas_width本身的,如果修改成相对于其它控件,会出问题。

bottomInnerView的约束如何添加。 希望width/height比为1/3.0,首先,设置了其topbottom与父视图一致且始终在父视图中居中显示,设置了bottomInnerView的高为宽的3倍,然后设置了宽和高与父视图相等,但是优先级为最低,以保证子视图的宽高不超过父视图,然后通过make.width.height.lessThanOrEqualTo设置了宽、高的最大值与父视图相同,:

     // width / height / 比为 1 / 3.0 要求是同一个控件的属性比例
[bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(bottomView); // 顶部和底部等于bottomView 的顶部和底部
make.center.mas_equalTo(bottomView); // 中心相等
// 注意,这个multipliedBy 的使用只能是设置同一个控件,比如这里的bottonInnerView
// 设置高 / 宽 为 3:1
make.height.mas_equalTo(bottomInnerView.mas_width).multipliedBy(); // 高度等于宽度乘以 3 make.width.height.mas_equalTo(bottomView).priorityLow(); // 宽和高设置优先级
make.width.height.lessThanOrEqualTo(bottomView); // 宽度和高度最高也小于bottomView 的高度和宽度
}];

接下篇...