iOS开发屏幕适配篇——masonry简介

一、为什么要学masonry(why)?

目前iOS开发中大多数页面都已经开始使用Interface Builder的方式进行UI开发了,但是在一些变化比较复杂的页面,还是需要通过代码来进行UI开发的。
而且有很多比较老的项目,本身就还在采用纯代码的方式进行开发。
而现在iPhone和iPad屏幕尺寸越来越多,虽然开发者只需要根据屏幕点进行开发,而不需要基于像素点进行UI开发。但如果在项目中根据不同屏幕尺寸进行各种判断,
写死坐标的话,这样开发起来是很吃力的。
所以一般用纯代码开发UI的话,一般都是配合一些自动化布局的框架进行屏幕适配。苹果为我们提供的适配框架有:VFL、UIViewAutoresizing、Auto Layout、
Size Classes等。
其中Auto Layout是使用频率最高的布局框架,但是其也有弊端。就是在使用UILayoutConstraint的时候,会发现代码量很多,而且大多都是重复性的代码,
以至于好多人都不想用这个框架。
后来Github上的出现了基于UILayoutConstraint封装的第三方布局框架Masonry,Masonry使用起来非常方便,本篇文章就详细讲一下Masonry的使用。

二、masonry是什么(what)?

Masonry是一个对系统NSLayoutConstraint进行封装的第三方自动布局框架,采用链式编程的方式提供给开发者API。系统AutoLayout支持的操作,Masonry都支持,
相比系统API功能来说,Masonry是有过之而无不及。
Masonry采取了链式编程的方式,代码理解起来非常清晰易懂,而且写完之后代码量看起来非常少。之前用NSLayoutConstraint写很多代码才能实现的布局,
用Masonry最少一行代码就可以搞定。下面看到Masonry的代码就会发现,太简单易懂了。
Masonry是同时支持Mac和iOS两个平台的,在这两个平台上都可以使用Masonry进行自动布局。我们可以从MASUtilities.h文件中,看到下面的定义,
这就是Masonry通过宏定义的方式,区分两个平台独有的一些关键字。
支持的属性:
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

 

  

 

三、怎样学习masonry(how)?

 1.masonry遇到的坑

在使用Masonry进行约束时,不用设置weak控制器self,因为masonry的内容使用的不是block,不存在循环引用。
在使用Masonry进行约束时,有一些是需要注意的。
在使用Masonry添加约束之前,需要在addSubview之后才能使用,否则会导致崩溃。
在添加约束时初学者经常会出现一些错误,约束出现问题的原因一般就是两种:约束冲突和缺少约束。对于这两种问题,可以通过调试和log排查。
之前使用Interface Builder添加约束,如果约束有错误直接就可以看出来,并且会以红色或者黄色警告体现出来。而Masonry则不会直观的体现出来,而是以运行过程中崩溃或者打印异常log体现,所以这也是手写代码进行AutoLayout的一个缺点。
这个问题只能通过多敲代码,积攒纯代码进行AutoLayout的经验,慢慢就用起来越来越得心应手了。

 Masonry 设置UILabel时多行一定要设置宽度。

2.基础API

mas_makeConstraints()    添加约束
mas_remakeConstraints()  移除之前的约束,重新添加新的约束
mas_updateConstraints()  更新约束

equalTo()       参数是对象类型,一般是视图对象或者mas_width这样的坐标系对象
mas_equalTo()   和上面功能相同,参数可以传递基础数据类型对象,可以理解为比上面的API更强大

width()         用来表示宽度,例如代表view的宽度
mas_width()     用来获取宽度的值。和上面的区别在于,一个代表某个坐标系对象,一个用来获取坐标系对象的值

3.修饰语句

Auto Boxing
上面例如equalTo或者width这样的,有时候需要涉及到使用mas_前缀,这在开发中需要注意作区分。
如果在当前类引入#import "Masonry.h"之前,用下面两种宏定义声明一下,就不需要区分mas_前缀。
// 定义这个常量,就可以不用在开发过程中使用"mas_"前缀。
#define MAS_SHORTHAND
// 定义这个常量,就可以让Masonry帮我们自动把基础数据类型的数据,自动装箱为对象类型。
#define MAS_SHORTHAND_GLOBALS
修饰语句
Masonry为了让代码使用和阅读更容易理解,所以直接通过点语法就可以调用,还添加了and和with两个方法。这两个方法内部实际上什么都没干,
只是在内部将self直接返回,功能就是为了更加方便阅读,对代码执行没有实际作用。 例如下面的例子: make.top.and.bottom.equalTo(self.containerView).with.offset(padding);

3.常用的方法

设置内边距

/** 
 设置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);
}];
通过insets简化设置内边距的方式

// 下面的方法和上面例子等价,区别在于使用insets()方法。
[self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
    // 下、右不需要写负号,insets方法中已经为我们做了取反的操作了。
    make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
}];

4.常用例子

  4.1 上下排列

    [self.view addSubview:self.blueView];
    [self.view addSubview:self.yellowView];
    [self.view addSubview:self.redView];

    //三个等高视图占一个屏幕,关键点inses内边距和高度可以同时等同于多个视图make.height.equalTo(@[self.redView,self.blueView]);
    //上下排列核心思想:起点redView视图固定上左右底,中间blueView固定左右底,底部yellowView固定 左右底,高度等高
1.基本用法

    CGFloat padding = 10;
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.top.equalTo(self.view).insets(UIEdgeInsetsMake(padding, padding, 0, padding));
        make.bottom.equalTo(self.blueView.mas_top).offset(-10);
    }];
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, 0, padding));
        make.bottom.equalTo(self.yellowView.mas_top).offset(-10);
    }];
    [self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, padding, padding));
        make.height.equalTo(@[self.redView,self.blueView]);
    }];

#pragma mark - 🔒private
- (NSString *)ranomText{
    
    CGFloat length = arc4random()%50 +5;
    NSMutableString *str = [[NSMutableString alloc] init];
    for (NSInteger i = 0; i <length; i++) {
        [str appendString:@"测试一下啦!"];
    }
    return str;
}
- (UIView *)blueView{
    if (!_blueView) {
        _blueView = [[UIView alloc] init];
        [_blueView setBackgroundColor:[UIColor blueColor]];
      
    }
    return _blueView;
}
- (UIView *)yellowView{
    if (!_yellowView) {
        _yellowView = [[UIView alloc] init];
        [_yellowView setBackgroundColor:[UIColor yellowColor]];
        
    }
    return _yellowView;
}
- (UIView *)redView{
    if (!_redView) {
        _redView = [[UIView alloc] init];
        [_redView setBackgroundColor:[UIColor redColor]];
        
    }
    return _redView;
}

 4.2 左右排列

   //左左排列核心思想:起点blueView固定中心、左右,高度及等宽,中间redView固定高度,中心及右边,最右边视图等高,右,中心位置
    CGFloat padding = 10.0f;
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view.mas_left).offset(padding);
        make.centerY.equalTo(self.view.mas_centerY);
        make.height.equalTo(@50);
        make.width.equalTo(@[self.redView,self.yellowView]);
        make.right.equalTo(self.redView.mas_left).with.offset(-padding);
    }];
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.height.equalTo(@50);
        make.right.equalTo(self.yellowView.mas_left).offset(-padding);
        make.centerY.equalTo(self.view.mas_centerY);
    }];
    [self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(@[self.blueView,self.redView]);
        make.height.equalTo(@[self.blueView,self.redView]);
        
        make.right.equalTo(self.view.mas_right).offset(-10);
    }];

 4.3 上下复合排列

    //左右下核心思想,首先总体结构是上下所以要设置等高,然后再设置上下左右的间隙,左1设置上左下右及高度,右1设置右下上就可以因为左边已经有约束,下blueView只需要设置左右下及高度
    CGFloat padding = 10.0f;
    [self.yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(self.view).insets(UIEdgeInsetsMake(padding, padding, 0, 0));
        make.right.equalTo(self.redView.mas_left).offset(-padding);
        make.bottom.equalTo(self.blueView.mas_top).offset(-padding);
        make.width.equalTo(self.redView.mas_width);
    }];
    [self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.right.equalTo(self.view).insets(UIEdgeInsetsMake(padding, 0,0,padding));
        make.bottom.equalTo(self.blueView.mas_top).offset(-padding);
    }];
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(0, padding, 0, padding));
        make.bottom.equalTo(self.view.mas_bottom);
        make.height.equalTo(self.yellowView.mas_height);
    }];

 4.4 约束比例, multipliedBy 只能设置同一控制的,如自身高度等于宽度的3倍,宽度*这个比例

 UIView *topView = [[UIView alloc] init];
    topView.backgroundColor = [UIColor redColor];
    [self.view addSubview:topView];
    
    UIView *topInnerView = [[UIView alloc] init];
    topInnerView.backgroundColor = [UIColor greenColor];
    [topView addSubview:topInnerView];
    
    UIView *bottomView = [[UIView alloc] init];
    bottomView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:bottomView];
    
    UIView *bottomInnerView = [[UIView alloc] init];
    bottomInnerView.backgroundColor = [UIColor blueColor];
    [bottomView addSubview:bottomInnerView];
    
    [topView mas_makeConstraints:^(MASConstraintMaker *make) {~~
        make.left.right.top.equalTo(self.view).width.offset(0);
        make.height.mas_equalTo(bottomView);
    }];
    [topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(topView);
     
        make.width.equalTo(topInnerView.mas_height).multipliedBy(3);
           make.center.equalTo(topView);
        //设置优先级,设置优先级时一定要加(),优先级 Masonry会优先实现优先级高的设定,发生冲突时,放弃优先级低的设定.
        make.width.height.mas_equalTo(topView).priorityLow();
        make.width.height.lessThanOrEqualTo(topView);
    }];
    [bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.equalTo(self.view);
        make.top.equalTo(topView.mas_bottom);
    
    }];
    [bottomInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.bottom.mas_equalTo(bottomView);
        make.center.equalTo(bottomView);
        make.height.equalTo(bottomInnerView.mas_width).multipliedBy(3);
        //设置优先级,lessThanOrEqualTo小于等于,优先级 Masonry会优先实现优先级高的设定,发生冲突时,放弃优先级低的设定.
        make.height.width.equalTo(bottomView).priorityLow();
        make.height.width.lessThanOrEqualTo(bottomView);
        
    }];

  4.5 点击放大,更新约束的使用

/**3.点击放大,知识点,更新约束,优先级,小于等于等
     核心1://初始化宽度,高为100,优先级最低
     make.width.height.mas_equalTo(100*self.scale).priorityLow();
     //最大放大到整个view,小于等于view的宽度和高度
     make.width.height.lessThanOrEqualTo(self.view);
     核心2:更新约束 ,告诉需要更新,检查是否需要更新,立即更新*/
@property (nonatomic, strong) UIButton *bigButton;
@property (nonatomic, assign) CGFloat scale;//比例

    self.scale = 1.0;
    self.bigButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.bigButton setTitle:@"点我放大" forState:UIControlStateNormal];
    self.bigButton.layer.borderColor = UIColor.greenColor.CGColor;
    self.bigButton.layer.borderWidth = 3;
    [self.bigButton addTarget:self action:@selector(onGrowButtonTaped:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.bigButton];
    
    [self.bigButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.mas_equalTo(self.view);
        //初始化宽度,高为100,优先级最低
        make.width.height.mas_equalTo(100*self.scale).priorityLow();
        //最大放大到整个view
        make.width.height.lessThanOrEqualTo(self.view);
    }];
#pragma mark - 🎬event response
- (void)onGrowButtonTaped:(UIButton *)sender{
    self.scale += 0.2;
    //告诉self.view约束需要更新
    [self.view setNeedsUpdateConstraints];
    // 调用此方法告诉self.view检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
    [self.view updateConstraintsIfNeeded];
    [UIView animateWithDuration:0.3 animations:^{
        [self.view layoutIfNeeded]; //立即更新
    }];
}
#pragma mark - updateViewConstraints
- (void)updateViewConstraints{
    
    [self.bigButton  mas_updateConstraints:^(MASConstraintMaker *make)  {
        make.center.equalTo(self.view);
        
        //设置优先级
        make.width.height.mas_equalTo(100*self.scale).priorityLow();
        make.width.height.lessThanOrEqualTo(self.view);
    }];
    //调用父类更新约束
    [super updateViewConstraints];
}

  

 4.6 scrollView的简单适配

   /**4. scrollView,核心思想就是先固定四周,然后再设置contentSize,在这里是底部
     核心1:preferredMaxLayoutWidth 最大的适配宽度
     核心2:scrollView的适配,首先先固定,然后设置底部的值 
     核心3:配置label顶部间距
     */
    self.scrollView = [[UIScrollView alloc] init];
    self.scrollView.pagingEnabled = NO;
    [self.view addSubview:self.scrollView];
    [self.scrollView setBackgroundColor:[UIColor lightGrayColor]];
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    UILabel *lastLabel = nil;
    for (NSInteger i = 0; i < 20; i++) {
        UILabel *label = [[UILabel alloc] init];
        label.numberOfLines = 0;
        label.layer.borderColor = [UIColor greenColor].CGColor;
        label.layer.borderWidth = 2.0;
        label.text = [self ranomText];
        label.preferredMaxLayoutWidth = screenWidth - 30;
        label.textAlignment = NSTextAlignmentLeft;
        [self.scrollView addSubview:label];
        
        [label mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.view.mas_left).offset(15);
            make.right.equalTo(self.view.mas_right).offset(-15);
            if (lastLabel) {//下一行是第一行最后一个label加上一定的间距
                make.top.mas_equalTo(lastLabel.mas_bottom).offset(20);
            }else{
                make.top.mas_equalTo(self.scrollView.mas_top).offset(20);
            }
            
        }];
        lastLabel = label;
    }
    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
        make.bottom.equalTo(lastLabel.mas_bottom).offset(20);
    }];
#pragma mark - 🔒private
- (NSString *)ranomText{
    
    CGFloat length = arc4random()%50 +5;
    NSMutableString *str = [[NSMutableString alloc] init];
    for (NSInteger i = 0; i <length; i++) {
        [str appendString:@"测试一下啦!"];
    }
    return str;
}  

 

 

 

posted @ 2017-05-17 19:13  TheYouth  阅读(2205)  评论(0编辑  收藏  举报