iOS开发之5个例子轻松学会利用Masonry实现代码约束(快速上手Autolayout)以及Masonry和MMPlaceHolder的简单介绍

前言

  • 纯手写代码一共经历了三个时期,这里我就不介绍了,随着iPhone6的出现,苹果手机的尺寸也就不在单一。为了让自己的软件能够完美适配各款机型,autolayout必然是页面布局的必然趋势,利用CGRectMake布局的局限性显得越来越大。

  • 那如何快速的上手autolayout呢? 当年iOS6推出的同时新增了autolayout的特性,现在已经发展到iOS10,可是我就一直没用过,我看了一下官方文档和demo 就立马抛弃到一边了,因为实在过于的繁琐和啰嗦!那么就引出了今天的主角-第三方库Masonry,经过查询加研究,我只经过几个小时的时间便已经可以熟练掌握了,这也就是我写这篇文章推荐它的真正原因,简单易懂,操作方便!

  • 文中的Demo我写在一个工程中,需要的请在文章末尾下载,如遇问题欢迎在评论区提问。

Masonry和MMPlaceHolder简单介绍

Masonry源码!

Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性 而且同时支持 iOS 和 Max OS X

Masonry属性与NSLayoutAttrubute的对照表如下:

Masonry属性 NSAutoLayout 说明
left NSLayoutAttributeLeft 左侧
top NSLayoutAttributeTop 上侧
right NSLayoutAttributeRight 右侧
bottom NSLayoutAttributeBottom 下侧
leading NSLayoutAttributeLeading 首部
trailing NSLayoutAttributeTrailing 尾部
width NSLayoutAttributeWidth
height NSLayoutAttributeHeight
centerX NSLayoutAttributeCenterX 横向中点
centerY NSLayoutAttributeCenterY 纵向中点
baseline NSLayoutAttributeBaseline 文本基线

其中leading与left trailing与right 在正常情况下是等价的 但是当一些布局是从右至左时(比如阿拉伯文?没有类似的经验) 则会对调 换句话说就是基本可以不理不用 用left和right就好了。

Masonry中能够添加autolayout约束有三个函数

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
 mas_makeConstraints 只负责新增约束 Autolayout不能同时存在两条针对于同一对象的约束 否则会报错
 mas_updateConstraints 针对上面的情况 会更新在block中出现的约束 不会导致出现两个相同约束的情况
 mas_remakeConstraints 则会清除之前的所有约束 仅保留最新的约束
 三种函数善加利用 就可以应对各种情况了

MMPlaceHolder源码!

  • 一行代码解决显示问题 简单易用
  • 搭建码农和设计之间的沟通桥梁 减少沟通成本(Talk is cheap. Show me the code.)
  • 显示大小自适应(最小支持30*30哦)

** MMPlaceHolder使用 **

- (void)showPlaceHolder;//常用
- (void)showPlaceHolderWithLineColor:(UIColor*)lineColor;
- (void)showPlaceHolderWithLineColor:(UIColor*)lineColor backColor:(UIColor*)backColor;
- (void)showPlaceHolderWithLineColor:(UIColor*)lineColor backColor:(UIColor*)backColor arrowSize:(CGFloat)arrowSize;

- (void)hidePlaceHolder;
- (MMPlaceHolder *)getPlaceHolder;

5个Demo轻松学会

  • 为了方便 我们测试的superView都是一个size为(300,300)的UIView
  • 下面通过5个简单的实例由浅入深教你如何轻松愉快的使用Masonry

1.[基础] 居中显示一个view

注:代码中有注释,因此代码我将不多做解释,还是不懂可以在文章评论区提问或者私信我!

- (void)viewDidLoad {
    [super viewDidLoad];
    //从此之后基本可以抛弃CGRectMake了
    UIView *sv = [UIView new];
    [sv showPlaceHolder];
    sv.backgroundColor = [UIColor blackColor];

  //在做autoLayout之前 一定要先将view添加到superview上 否则会报错
    [self.view addSubview:sv];

    //mas_makeConstraints就是Masonry的autolayout添加函数 将所需的约束添加到block中行了
    [sv mas_makeConstraints:^(MASConstraintMaker *make) {

        //将sv居中(很容易理解吧?)
        make.center.equalTo(self.view);

         //将size设置成(300,300)
        make.size.mas_equalTo(CGSizeMake(300, 300));
    }];
}

效果图:


让一个view居中显示.png
  • 这里有个问题,equalTo 和 mas_equalTo的区别在哪里呢? 其实 mas_equalTo就是一个MACRO
#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...)    greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...)       lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...)                  valueOffset(MASBoxValue((__VA_ARGS__)))
  • 可以看到mas_equalTo只是对其参数进行了一个BOX操作(装箱) MASBoxValue的定 义具体可以看看源代码 太长就不贴出来了
  • 所支持的类型 除了NSNumber支持的那些数值类型之外 就只支持CGPoint CGSize UIEdgeInsets

2.[初级] 让一个view略小于其superView(边距为10)

- (void)viewDidLoad {
    [super viewDidLoad];
//此处sv作为superView
    UIView *sv = [UIView new];
    sv.backgroundColor = [UIColor blackColor];
    [self.view addSubview:sv];
    [sv mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(300, 300));
    }];
    
    UIView *sv1 = [UIView new];
    [sv1 showPlaceHolder];
    sv1.backgroundColor = [UIColor redColor];
    [sv addSubview:sv1];
    [sv1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
        /* 等价于
         make.top.equalTo(sv).with.offset(10);
         make.left.equalTo(sv).with.offset(10);
         make.bottom.equalTo(sv).with.offset(-10);
         make.right.equalTo(sv).with.offset(-10);
         */
        
        /* 也等价于
         make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
         */
    }];
}

效果图


让一个view略小于其supeView.png
  • 可以看到 edges 其实就是top,left,bottom,right的一个简化 分开写也可以 一句话更省事。

  • 那么为什么bottom和right里的offset是负数呢? 因为这里计算的是绝对的数值 计算的bottom需要小于sv的底部高度 所以要-10 同理用于right。

  • 这里有意思的地方是and和with 其实这两个函数什么事情都没做。

- (MASConstraint *)with {
    return self;
}
- (MASConstraint *)and {
    return self;
}
  • 但是用在这种链式语法中 就非常的巧妙和易懂 不得不佩服作者的心思(看不懂的请忽略)。

3.[初级] 让两个高度为150的view水平居中且等宽且等间隔排列 间隔为10(自动计算其宽度)

*vc中代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    UIView *sv = [UIView new];
    sv.backgroundColor = [UIColor blackColor];
    [self.view addSubview:sv];
    [sv mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(300, 300));
    }];
    
    int padding1 = 10;
    
    UIView *sv2 = [UIView new];
    [sv2 showPlaceHolder];
    sv2.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:sv2];
    
    UIView *sv3 = [UIView new];
    [sv3 showPlaceHolder];
    sv3.backgroundColor = [UIColor orangeColor];
    [self.view addSubview:sv3];
    
    [sv2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.mas_equalTo(sv.mas_centerY);
        make.left.equalTo(sv.mas_left).with.offset(padding1);
        make.right.equalTo(sv3.mas_left).with.offset(-padding1);
        make.height.mas_equalTo(@150);
        make.width.equalTo(sv3);
    }];
    
    [sv3 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.mas_equalTo(sv.mas_centerY);
        make.left.equalTo(sv2.mas_right).with.offset(padding1);
        make.right.equalTo(sv.mas_right).with.offset(-padding1);
        make.height.mas_equalTo(@150);
        make.width.equalTo(sv2);
    }];

}

效果图:


让两个view水平居中等宽排列.png
  • 这里我们在两个子view之间互相设置的约束 可以看到他们的宽度在约束下自动的被计算出来了。

4.[中级] 在UIScrollView顺序排列一些view并自动计算contentSize

  • vc中代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    UIView *sv = [UIView new];
    sv.backgroundColor = [UIColor blackColor];
    [self.view addSubview:sv];
    [sv mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(300, 300));
    }];
    
    UIScrollView *scrollView = [UIScrollView new];
    scrollView.backgroundColor = [UIColor whiteColor];
    [sv addSubview:scrollView];
    [scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(5,5,5,5));
    }];
    
    UIView *container = [UIView new];
    [scrollView addSubview:container];
    [container mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(scrollView);
        make.width.equalTo(scrollView);
    }];
    int count = 10;

    UIView *lastView = nil;
    
    for ( int i = 1 ; i <= count ; ++i )
    {
        UIView *subv = [UIView new];
        [container addSubview:subv];
        subv.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
                                          saturation:( arc4random() % 128 / 256.0 ) + 0.5
                                          brightness:( arc4random() % 128 / 256.0 ) + 0.5
                                               alpha:1];
        
        [subv mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.and.right.equalTo(container);
            make.height.mas_equalTo(@(20*i));
            
            if ( lastView )
            {
                make.top.mas_equalTo(lastView.mas_bottom);
            }
            else
            {
                make.top.mas_equalTo(container.mas_top);
            }
        }];
        
        lastView = subv;
    }
    [container mas_makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(lastView.mas_bottom);
    }];

}

效果图如下:


UIScrollView排列View并自动计算contentSize.png
  • 从scrollView的scrollIndicator可以看出 scrollView的内部已如我们所想排列好了。
  • 这里的关键就在于container这个view起到了一个中间层的作用 能够自动的计算uiscrollView的contentSize。

5. [高级] 横向或者纵向等间隙的排列一组view

  • 很遗憾 autoLayout并没有直接提供等间隙排列的方法(Masonry的官方demo中也没有对应的案例) 但是参考案例3 我们可以通过一个小技巧来实现这个目的 为此我写了一个Category。
//
//  UIView+ZXB.m
//  DemoOfMasonry
//
//  Created by Mars_ZXB on 16/8/18.
//  Copyright © 2016年 BAT3. All rights reserved.
//
#import "UIView+ZXB.h"
#import "Masonry.h"
@implementation UIView (ZXB)
- (void) distributeSpacingHorizontallyWith:(NSArray*)views
{
    NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count+1];
    
    for ( int i = 0 ; i < views.count+1 ; ++i )
    {
        UIView *v = [UIView new];
        [spaces addObject:v];
        [self addSubview:v];
        
        [v mas_makeConstraints:^(MASConstraintMaker *make) {
            make.width.equalTo(v.mas_height);
        }];
    }
    
    UIView *v0 = spaces[0];
    
    [v0 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.mas_left);
        make.centerY.equalTo(((UIView*)views[0]).mas_centerY);
    }];
    
    UIView *lastSpace = v0;
    for ( int i = 0 ; i < views.count; ++i )
    {
        UIView *obj = views[i];
        UIView *space = spaces[i+1];
        
        [obj mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(lastSpace.mas_right);
        }];
        
        [space mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(obj.mas_right);
            make.centerY.equalTo(obj.mas_centerY);
            make.width.equalTo(v0);
        }];
        
        lastSpace = space;
    }
    
    [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.mas_right);
    }];
    
}

- (void) distributeSpacingVerticallyWith:(NSArray*)views
{
    NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count+1];
    
    for ( int i = 0 ; i < views.count+1 ; ++i )
    {
        UIView *v = [UIView new];
        [spaces addObject:v];
        [self addSubview:v];
        
        [v mas_makeConstraints:^(MASConstraintMaker *make) {
            make.width.equalTo(v.mas_height);
        }];
    }

    UIView *v0 = spaces[0];

    [v0 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.mas_top);
        make.centerX.equalTo(((UIView*)views[0]).mas_centerX);
    }];
    
    UIView *lastSpace = v0;
    for ( int i = 0 ; i < views.count; ++i )
    {
        UIView *obj = views[i];
        UIView *space = spaces[i+1];
        
        [obj mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(lastSpace.mas_bottom);
        }];
        
        [space mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(obj.mas_bottom);
            make.centerX.equalTo(obj.mas_centerX);
            make.height.equalTo(v0);
        }];
        
        lastSpace = space;
    }
    
    [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(self.mas_bottom);
    }];
}
@end
  • 下面开始在vc中布局,记得引用类目!
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *sv = [UIView new];
    [sv showPlaceHolder];
    sv.backgroundColor = [UIColor blackColor];
    [self.view addSubview:sv];
    [sv mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);
        make.size.mas_equalTo(CGSizeMake(300, 300));
    }];
    
    UIView *sv11 = [UIView new];
    UIView *sv12 = [UIView new];
    UIView *sv13 = [UIView new];
    UIView *sv21 = [UIView new];
    UIView *sv31 = [UIView new];
    
    sv11.backgroundColor = [UIColor redColor];
    sv12.backgroundColor = [UIColor redColor];
    sv13.backgroundColor = [UIColor redColor];
    sv21.backgroundColor = [UIColor redColor];
    sv31.backgroundColor = [UIColor redColor];
    
    [sv addSubview:sv11];
    [sv addSubview:sv12];
    [sv addSubview:sv13];
    [sv addSubview:sv21];
    [sv addSubview:sv31];
    
    //给予不同的大小 测试效果
    
    [sv11 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(@[sv12,sv13]);
        make.centerX.equalTo(@[sv21,sv31]);
        make.size.mas_equalTo(CGSizeMake(40, 40));
    }];
    
    [sv12 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(70, 20));
    }];
    
    [sv13 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(50, 50));
    }];
    
    [sv21 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(50, 20));
    }];
    
    [sv31 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(40, 60));
    }];
    
    [sv distributeSpacingHorizontallyWith:@[sv11,sv12,sv13]];
    [sv distributeSpacingVerticallyWith:@[sv11,sv21,sv31]];
    
    [sv showPlaceHolderWithAllSubviews];
    [sv hidePlaceHolder];

}

效果图:


多个view横向或纵向等间隙排列.png
  • 完美!!!简洁明了的达到了我们所要的效果。
  • 这里所用的技巧就是 使用空白的占位view来填充我们目标view的旁边 这点通过图上的空白标注可以看出来。

总结

  • 通过以上5个案例 我觉得已经把Masonry的常用功能介绍得差不多了, 如果你觉得意犹未尽,请下载官方的demo来学习.
  • 总而言之 Masonry是一个非常优秀的autolayout库,能够节省大量的开发和学习时间,尤其适合不习惯xib、sb编程的朋友, Masonry一定可以助你一臂之力!
  • 我将文中的5个例子写到了一个工程中,怕有的朋友进不去GitHup,特传到网盘中,有需要的可以下载看看,遇到问题可在评论区提问。文中的Demo代码下载 (提取码:61b3)
  • 感谢里脊串大神的博文,让我当时轻松学会,大神博客地址!

推荐阅读更多精彩内容