Masory了解与使用

零 · 引子:

  • 目前iOS开发中大多数页面都已经开始使用Interface Builder的方式(IB可简单理解为可视化编程,就是有控制面板,可以拖控件的布局方式)进行UI开发了,但是在一些变化比较复杂的页面,还是需要通过代码来进行UI开发的。
  • 因为现在iPhone和iPad屏幕尺寸越来越多(当然,相对于安卓已经简单很多了),如果在项目中用frame根据不同屏幕尺寸进行各种判断,写死坐标的话,这样开发起来是很吃力的,所以一般用纯代码开发UI的话,一般都是配合一些自动化布局的框架进行屏幕适配。苹果为我们提供的适配框架有:VFL、UIViewAutoresizing、Auto Layout、Size Classes等。
  • 其中Auto Layout是使用频率最高的布局框架,但是其也有弊端--就是在使用UILayoutConstraint的时候,会发现代码量很多,而且大多都是重复性的代码,以至于好多人都不想用这个框架。
  • 后来Github上的出现了基于UILayoutConstraint封装的第三方布局框架Masonry,Masonry使用起来非常方便,本篇文章主要讲述我对Masonry的理解和应用。

一 · Masonry简介

  • Masonry是一个对系统NSLayoutConstraint进行封装的第三方自动布局框架,采用链式编程的方式提供给开发者API。系统AutoLayout支持的操作,Masonry都支持,相比系统API功能来说,Masonry是有过之而无不及。
  • Masonry采取了链式编程的方式,代码理解起来非常清晰易懂,而且相对于NSLayoutConstraint来说,代码十分精简。
  • Masonry是同时支持Mac和iOS两个平台的,在这两个平台上都可以使用Masonry进行自动布局。我们可以从MASUtilities.h文件中,看到下面的定义,这就是Masonry通过宏定义的方式,区分两个平台独有的一些关键字。
MASUtilities.h 截图

二 · Masonry集成方式

三 · Masonry学习建议

  1. 在使用Masonry添加约束之前,被约束的视图必须有父控件,也就是说需要在addSubview之后才能使用,否则会导致崩溃。

  2. 其中约束项都必须是UIView及其子类的实例。

  3. 在添加约束时初学者经常会出现一些错误,约束出现问题的原因一般就是两种:约束冲突和缺少约束。对于这两种问题,可以通过调试和log排查。

  4. 我一般都是在控制器里导入 #import "Masonry.h"之前再添加两个宏,不仅可以提高App的开发效率,而且可以避免一些失误。

//1. 对于约束参数可以省去"mas_"
     #define MAS_SHORTHAND
//2. 对于默认的约束参数自动装箱Auto Boxing
    #define MAS_SHORTHAND_GLOBALS
    #import "Masonry.h" //上面两个宏必须添加在头文件前面

但是针对 “忽略前缀mas_的添加问题" 这个还有下面这个说法:

MAS_SHORTHAND,只要#define这个宏,就不用带mas_前缀了,但是不建议去掉,比如view.mas_left->view.left,这样容易和工程中其他view的category产生冲突。
MAS_SHORTHAND_GLOBALS,只要#define这个宏,qualTo就等价于mas_equalTo,后面就可以直接使用基本类型,而不需要转换成NSNumber了,建议添加。
  1. 使用Masonry不需要设置控件的translatesAutoresizingMaskIntoConstraints属性为NO;
  2. 防止block中的循环引用,使用弱引用(这是错误观点),在这里block是局部的引用,block内部引用self不会造成循环引用的
    __weak typeof (self) weakSelf = self;(没必要的写法)

四 · 约束的三种方法

 // 这个方法只会添加新的约束
 [blueView mas_makeConstraints:^(MASConstraintMaker *make)  {
 }];
 // 这个方法会将以前的所有约束删掉,添加新的约束
 [blueView mas_remakeConstraints:^(MASConstraintMaker *make) {
 }];
 // 这个方法将会覆盖以前的某些特定的约束
 [blueView mas_updateConstraints:^(MASConstraintMaker *make) {
 }];

五 · 常见约束的各种类型

1.尺寸:width、height、size
2.边:left、leading、right、trailing、top、bottom
3.中心点:center、centerX、centerY
4.边界:edges
5.偏移量:offset、insets、sizeOffset、centerOffset
6.priority()约束优先级(0~1000),multipler乘因数, dividedBy除因数
7.equalTo()       参数是对象类型,一般是视图对象或者mas_width这样的坐标系对象
8.mas_equalTo()   和上面功能相同,参数可以传递基础数据类型对象,可以理解为比上面的API更强大
9.width()         用来表示宽度,例如代表view的宽度
10.mas_width()     用来获取宽度的值。和上面的区别在于,一个代表某个坐标系对象,一个用来获取坐标系对象的值

六 · 更新约束和布局

关于更新约束布局相关的API,主要用以下四个API:

1.- (void)updateConstraintsIfNeeded  调用此方法,如果有标记为需要重新布局的约束,则立即进行重新布局,内部会调用updateConstraints方法
2.- (void)updateConstraints          重写此方法,内部实现自定义布局过程
3.- (BOOL)needsUpdateConstraints     当前是否需要重新布局,内部会判断当前有没有被标记的约束
4.- (void)setNeedsUpdateConstraints  标记需要进行重新布局

关于UIView重新布局相关的API,主要用以下三个API:

1.- (void)setNeedsLayout  标记为需要重新布局
2.- (void)layoutIfNeeded  查看当前视图是否被标记需要重新布局,有则在内部调用layoutSubviews方法进行重新布局
3.- (void)layoutSubviews  重写当前方法,在内部完成重新布局操作

七 · Masonry约束控件出现冲突的问题

当约束冲突发生的时候,我们可以设置view的key来定位:

redView.mas_key = @"redView";
greenView.mas_key = @"greenView";
blueView.mas_key = @"blueView";

//若是觉得这样一个个设置比较繁琐,怎么办呢,Masonry则提供了批量设置的宏:MASAttachKeys;

MASAttachKeys(redView,greenView,blueView); //一句代码即可全部设置

八 · 常用方法

  1. 设置内边距
/** 
 设置yellow视图和self.view等大,并且有10的内边距。
 注意根据UIView的坐标系,下面right和bottom进行了取反。所以不能写成下面这样,否则right、bottom这两个方向会出现问题。
 make.edges.equalTo(self.view).with.offset(10);
 
 除了下面例子中的offset()方法,还有针对不同坐标系的centerOffset()、sizeOffset()、valueOffset()之类的方法。
 */
[self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(self.view).with.offset(10);
    make.top.equalTo(self.view).with.offset(10);
    make.right.equalTo(self.view).with.offset(-10);
    make.bottom.equalTo(self.view).with.offset(-10);
}];
  1. 通过insets简化设置内边距的方式
// 下面的方法和上面例子等价,区别在于使用insets()方法。
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
    // 下、右不需要写负号,insets方法中已经为我们做了取反的操作了。
    make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
}];
  1. 更新约束
// 设置greenView的center和size,这样就可以达到简单进行约束的目的
[self.greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.center.equalTo(self.view);
    // 这里通过mas_equalTo给size设置了基础数据类型的参数,参数为CGSize的结构体
    make.size.mas_equalTo(CGSizeMake(300, 300));
}];
 
// 为了更清楚的看出约束变化的效果,在显示两秒后更新约束。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self.greenView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view).offset(100);
        make.size.mas_equalTo(CGSizeMake(100, 100));
    }];
});
  1. 大于等于和小于等于某个值的约束
textLabel只需要设置一个属性即可:
self.textLabel.numberOfLines = 0;

[self.textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    make.center.equalTo(self.view);
    // 设置宽度小于等于200
    make.width.lessThanOrEqualTo(@200);
    // 设置高度大于等于10
    make.height.greaterThanOrEqualTo(@(10));
}];
  1. 使用基础数据类型当做参数
/** 
 如果想使用基础数据类型当做参数,Masonry为我们提供了"mas_xx"格式的宏定义。
 这些宏定义会将传入的基础数据类型转换为NSNumber类型,这个过程叫做封箱(Auto Boxing)。
  
 "mas_xx"开头的宏定义,内部都是通过MASBoxValue()函数实现的。
 这样的宏定义主要有四个,分别是mas_equalTo()、mas_offset()和大于等于、小于等于四个。
 */
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.center.equalTo(self.view);
    make.width.mas_equalTo(100);
    make.height.mas_equalTo(100);
}];
  1. 设置约束优先级
/** 
 Masonry为我们提供了三个默认的方法,priorityLow()、priorityMedium()、priorityHigh(),这三个方法内部对应着不同的默认优先级。
 除了这三个方法,我们也可以自己设置优先级的值,可以通过priority()方法来设置。
 */
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.center.equalTo(self.view);
    make.width.equalTo(self.view).priorityLow();
    make.width.mas_equalTo(20).priorityHigh();
    make.height.equalTo(self.view).priority(200);
    make.height.mas_equalTo(100).priority(1000);
}];

Masonry也帮我们定义好了一些默认的优先级常量,分别对应着不同的数值,优先级最大数值是1000。
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
  1. 设置约束比例
// 设置当前约束值乘以多少,例如这个例子是redView的宽度是self.view宽度的0.2倍。
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.center.equalTo(self.view);
    make.height.mas_equalTo(30);
    make.width.equalTo(self.view).multipliedBy(0.2);
}];

一些例子


//设置约束
- (void)demo1
{
//    [self.yelloewView mas_makeConstraints:^(MASConstraintMaker *make) {
//        make.left.and.top.equalTo(self.view).with.offset(10);
//        make.right.and.bottom.equalTo(self.view).with.offset(-10);
//    }];
    
    [self.yelloewView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
    }];
}

//更新约束
- (void)demo2
{
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(100, 100));
    }];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.redView mas_updateConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(CGSizeMake(150, 150));
        }];
    });
}

//最大最小限制
- (void)demo3
{
    [self.testLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.width.lessThanOrEqualTo(@100);
        make.height.greaterThanOrEqualTo(@(10));

    }];
}

//设置约束优先级
- (void)demo4
{
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view).priorityHigh();
        make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 50, 10, 110)).priorityLow();
    }];
}

//设置约束比例
- (void)demo5
{
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.width.height.equalTo(self.view).multipliedBy(0.3);
     }];
}

//视图等高
- (void)demo6
{
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.right.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 10, 0, 10));
        make.bottom.equalTo(self.blueView.mas_top).offset(-10);
    }];
    
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.view).with.insets(UIEdgeInsetsMake(0, 10, 0, 10));
        make.bottom.equalTo(self.yelloewView.mas_top).with.offset(-10);
    }];
    
    [self.yelloewView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.equalTo(self.view).with.insets(UIEdgeInsetsMake(0, 10, 10, 10));
        make.height.equalTo(@[self.redView,self.blueView]);
    }];
}
//视图居中
- (void)demo7
{
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.view);
        make.left.equalTo(self.view).mas_offset(10);
        make.right.equalTo(self.blueView.mas_left).mas_offset(-10);
        make.width.equalTo(self.blueView);
        make.height.equalTo(self.blueView);
    }];
    
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.view);
        make.right.equalTo(self.view).mas_offset(-10);
        make.height.mas_equalTo(150);
    }];
}

//scrollview自适应
- (void)demo8
{ self.scrollView = [[UIScrollView alloc] init];
    [self.view addSubview:self.scrollView];
    [self.scrollView addSubview:self.containerView];

    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    CGFloat padding = LXZViewPadding;
    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.scrollView).insets(UIEdgeInsetsMake(padding, padding, padding, padding));
    }];
    
    [self.containerView addSubview:self.blueView];
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.equalTo(self.containerView).offset(padding);
        make.size.mas_equalTo(CGSizeMake(250, 250));
    }];
    
    [self.containerView addSubview:self.redView];
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.containerView).offset(padding);
        make.left.equalTo(self.blueView.mas_right).offset(padding);
        make.size.equalTo(self.blueView);
        make.right.equalTo(self.containerView).offset(-padding);
    }];
    
    [self.containerView addSubview:self.yelloewView];
    [self.yelloewView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.containerView).offset(padding);
        make.top.equalTo(self.blueView.mas_bottom).offset(padding);
        make.size.equalTo(self.blueView);
        make.bottom.equalTo(self.containerView).offset(-padding);
    }];
}


- demo9
多个(2个以上)控件的等间隔排序显示

首先介绍2个函数

/**
    *  axisType         轴线方向
    *  fixedSpacing     间隔大小
    *  fixedItemLength  每个控件的固定长度/宽度
    *  leadSpacing      头部间隔
    *  tailSpacing      尾部间隔
    *
    */
//1. 等间隔排列 - 多个控件间隔固定,控件长度/宽度变化
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing
tailSpacing:(CGFloat)tailSpacing;

//2. 等间隔排列 - 多个固定大小固定,间隔空隙变化
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
withFixedItemLength:(CGFloat)fixedItemLength
leadSpacing:(CGFloat)leadSpacing
tailSpacing:(CGFloat)tailSpacing;
//首先添加5个视图
 NSMutableArray *array = [NSMutableArray new];
    for (int i = 0; i < 5; i ++) {
        UIView *view = [UIView new];
        view.backgroundColor = [UIColor greenColor];
        [self addSubview:view];
        [array addObject:view]; //保存添加的控件
    }

//水平方向控件间隔固定等间隔
[array mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:15 leadSpacing:10 tailSpacing:10];
            [array makeConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(50);
                make.height.equalTo(70);
            }];

//水平方向宽度固定等间隔
[array mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:70 leadSpacing:10 tailSpacing:10];
            [array makeConstraints:^(MASConstraintMaker *make) { //数组额你不必须都是view 
                make.top.equalTo(50);
                make.height.equalTo(70);
            }];

- demo10
 多行label的约束问题

对于`UILabel`文字内容多的问题,个人觉得`Masonry`约束设置的非常简单优雅,在此非常感谢`Masonry`的作者[@Robert Payne](https://link.jianshu.com?t=https://github.com/robertjpayne)

    //创建label
    self.label = [UILabel new];
    self.label.numberOfLines = 0;
    self.label.lineBreakMode = NSLineBreakByTruncatingTail;
    self.label.text = @"有的人,没事时喜欢在朋友圈里到处点赞,东评论一句西评论一句,比谁都有存在感。等你有事找他了,他就立刻变得很忙,让你再也找不着。真正的朋友,平常很少联系。可一旦你遇上了难处,他会立刻回复你的消息,第一时间站出来帮你。所谓的存在感,不是你有没有出现,而是你的出现有没有价值。存在感,不是刷出来的,也不是说出来的。有存在感,未必是要个性锋芒毕露、甚至锋利扎人。翩翩君子,温润如玉,真正有存在感的人,反而不会刻意去强调他的存在感。他的出现,永远都恰到好处。我所欣赏的存在感,不是长袖善舞巧言令色,而是对他人的真心关照;不是锋芒毕露计较胜负,而是让人相处得舒服;不是时时刻刻聒噪不休,而是关键时刻能挺身而出。别总急着出风头,希望你能有恰到好处的存在感。";
    [self addSubview: self.label];

    [self.label makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(10);
        make.right.equalTo(-10);
    }];

//添加约束
- (void)layoutSubviews {
//1. 执行 [super layoutSubviews];
    [super layoutSubviews];

//2. 设置preferredMaxLayoutWidth: 多行label约束的完美解决
   self.label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20;

//3. 设置preferredLayoutWidth后,需要再次执行 [super layoutSubviews]; 
    //其实在实际中这步不写,也不会出错,官方解释是说设置preferredLayoutWidth后需要重新计算并布局界面,所以这步最好执行
    [super layoutSubviews];
}

我的推荐

Masonry学习笔记和使用技巧(cell高度自适应,多控件居中、等间距分布、UIScrollView约束问题)--这篇文章基础解释不多,但写的例子比我的好很多,对实际操作很有帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容