Masonry

作者: 我有小尾巴快看 | 来源:发表于2017-11-06 17:45 被阅读85次

    Masonry是一个轻量级的布局框架,它拥有自己的描述语法(链式语法)来自动布局,具有很好可读性且同时支持iOS和Max OS X等。
    官方示例地址:https://github.com/Masonry/Masonry
    Masonry框架虽然很轻量,但是代码中使用了大量的block和链式编程的技巧,所以下面简单介绍链接语法以及block。
    参考了http://www.jianshu.com/p/e07dcea6beae以及其他人的内容。

    1.Block的常见使用

    • 声明block类型变量
    int (^test)(int) = ^(int a) {
      return a + 1;
    };
    
    • block作为函数参数
    - (void)test :(void (^)(NSString *a)) block { 
    
    }
    
    • block作为函数返回值
     - (void (^)(NSString *))block {
        return ^(NSString *a) {
            NSLog(@"%@",a);
        };
    }
    
    • 有时候我们也可以使用typedef来简化block
     typedef void(^block)(NSString *a);
    
     - (block)block {
        return ^(NSString *a) {
        NSLog(@"%@",a);
        };
    }
    
    - (void)test :(block)block {
    
    }
    

    2.链式编程思想

    很多地方总会将链式编程和函数式编程结合在一起来谈论,这里先简单说明下两者的区别。
    • 函数式编程:
      将函数作为和其他数据类型一样来进行操作,可以作为其他函数的参数和返回值等,例如:
     func1(func2(func3(8)));
    
    • 链式编程
      用点的形式连接函数,完成参数的传递和逻辑处理,例如:
     make.left.equalTo(self.view.mas_left).with.offset(18);
    

    接着我们来看链式编程是如何实现的,使用人的例子,我们先来看看整体的代码,再逐步分析:

    • .h文件
    @property (nonatomic,assign) NSUInteger monry;
    
    + (NSUInteger)life:(void(^)(Person * make))block;
    
    - (Person * (^) (NSUInteger))get;
    - (Person * (^) (NSUInteger))use;
    - (instancetype)initWithMoney:(NSUInteger)monry;
    - (instancetype)initWithGold;
    
    • .m文件
    - (instancetype)initWithGold {
        if (self = [super init]) {
            self.monry = 100;
        }
        return self;
    }
    
    - (instancetype)initWithMoney:(NSUInteger)monry {
        if (self = [super init]) {
            self.monry = monry;
        }
        return self;
    }
    
    #pragma mark -
    
    + (NSUInteger)life:(void(^)(Person * make))block {
        Person *man = [[Person alloc] init];
        block(man);
        return man.monry;
    }
    
    - (Person * (^) (NSUInteger))get {
        return ^(NSUInteger money) {
            self.monry += money;
            return self;
        };
    }
    
    - (Person * (^) (NSUInteger))use {
        return ^(NSUInteger money) {
            self.monry -= money;
            return self;
        };
    }
    
    • 使用
    [Person life:^(Person *make) {
        make.use(5).get(1);
    }];
    

    看.h文件,有一个money属性来存储剩余的钱,

    • 类方法:life
      使用了block做参数,block中有一个Person对象
      在实现中:先初始化Person对象,该对象作为block参数并且调用block,最后返回刚刚对象的money属性。

    • 返回block的方法:get、use
      使用了block作为返回值
      实现中:操作成员变量money,返回自身(链式编程的核心)

    • 链中的方法:man.use(10).get(20);
      可以发现在链中的调用都是通过点进行的,但是在OC中都是通过
      [object method] 的形式调用的,这里分为两种:
      返回值为block的方法:
      首先该方法返回值是一个block,调用起来相当于getter方法,所以等价于一个block的属性

     @property (nonatomic, readonly) Person * (^get) (NSUInteger);
    

    调用过程可以分为两步:
    1.(Person * (^)(NSUInteger)) tempBlock = make.get;这里make.get是获取了属性
    2.tempBlock(4);
    返回值为对象的方法:
    在OC中,如果符合getter方法的格式(点语法在等号左边时为setter,否则为getter),则可以通过点语法进行调用。

    3.Masonry基本使用

    在传统的initWithRect方法中,设置view的frame所使用的rect由CGRectMake方法创建,该方法由左上角的点以及宽高共四个参数来确定view的位置。在自动布局中,也是需要满足四个条件来确定view的frame。
    
    • 有以下常见属性
    @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;   //文本基线
    
    • 常见约束的各种类型

    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除因数

    • 以下是常用的修改约束的方法
    //新增约束(只能新增一次)
     - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
    
    //更新约束
     - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
    
    //清除之前所有的约束,并且添加新约束
     - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
    

    添加约束前必须要先被添加到一个视图中!否则会崩溃。

    • Masonry的简单应用
     [view mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(self.mas_left).offset(10);
            make.right.equalTo(self.mas_right).offset(-10);
            make.top.equalTo(self.mas_top).offset(10);
            make.bottom.equalTo(self.mas_bottom).offset(-10);
     }];   
    

    以上创建了一个上下左右都相距俯视图10个点的view,也可以通过以下方法简化。

     [view mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.mas_offset(UIEdgeInsetsMake(10,10,10,10));
     }];   
    
    • equalTo & mas_equalTo
    #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__)))
    

    equalTo仅支持基本类型
    mas_equalTo支持类型转换,支持复杂类型。是对equalTo的封装。支持CGSize、CGPoint、NSNumber、UIEdgeinsets。

    mas_equalTo是一个Macro,比较值
    equalTo比较View。
    以下实现的是相同的效果
    make.bottom.mas_equalTo(self.view.mas_bottom);
    make.bottom.equalTo(self.view);

    去掉mas_前缀,只用equalTo即可,添加到.prefix文件
    // 添加这个宏,就不用带mas_前缀
    #define MAS_SHORTHAND
    // 添加这个宏,equalTo就等价于mas_equalTo
    #define MAS_SHORTHAND_GLOBALS
    // 这个头文件一定要放在上面两个宏的后面
    #import "Masonry.h"

    • 对一个数组进行约束
    /**
         *  axisType             方向
         *  fixedSpacing         间隔
         *  fixedItemLength      长/宽
         *  leadSpacing          头部间隔
         *  tailSpacing          尾部间隔
         */
    //等间隔排列,拉伸控件的长/宽来适应
    - (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
    //等长/宽排列 ,拉伸控件组之间的间隔来适应
    - (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
    
    • Masonry约束易忽略的技术点

    使用Masonry不需要设置控件的translatesAutoresizingMaskIntoConstraints属性为NO;
    防止block中的循环引用,使用弱引用(这是错误观点),在这里block是局部的引用,block内部引用self不会造成循环引用的
    __weak typeof (self) weakSelf = self;(没必要的写法)

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

    当约束冲突发生的时候,我们可以设置view的key来定位是哪个view
    redView.mas_key = @"redView";
    greenView.mas_key = @"greenView";
    blueView.mas_key = @"blueView";
    若是觉得这样一个个设置比较繁琐,怎么办呢,Masonry则提供了批量设置的宏MASAttachKeys
    MASAttachKeys(redView,greenView,blueView); //一句代码即可全部设置

    • 苹果官方建议:添加/更新约束在updateConstraints方法内
    // this is Apple's recommended place for adding/updating constraints
    - (void)updateConstraints {
       //更新约束
        [view updateConstraints:^(MASConstraintMaker *make) {
            make.top.bottom.left.right.equalTo(self);
        }];
        [super updateConstraints];//最后必须调用父类的更新约束
    }
    
    //若视图基于自动布局的,则需要重写这个方法为YES(默认NO)
    + (BOOL)requiresConstraintBasedLayout{
        return YES ; 
    }
    
    • 使用约束需要注意的地方
    1. setNeedsUpdateConstraints
      当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后调用updateConstraints.
    2. needsUpdateConstraints
      constraint-based layout system使用此返回值去决定是否需要调用updateConstraints作为正常布局过程的一部分。
    3. updateConstraintsIfNeeded
      在有setNeedsUpdateConstraints标记的情况下,立即触发约束更新,自动更新布局。
    4. updateConstraints
      应该在此处建立或更新约束,最后需要调用[super updateConstraints]
    5. setNeedsLayout
      标记未来将要重新布局,系统会自动调用layoutSubviews更新布局。
    6. layoutIfNeeded
      如果有标记则立即调用layoutSubviews进行布局
    • Auto Layout Process & springs and struts(autoresizingMask)的区别

    Auto Layout Process 自动布局过程与使用springs and struts(autoresizingMask)比较,Auto layout在view显示之前,多引入了两个步骤:updating constraints 和laying out views。每一个步骤都依赖于上一个。display依赖layout,而layout依赖updating constraints。 updating constraints->layout->display

    1. updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。
      自定义view的话,通常可以重写updateConstraints方法,在其中可以添加view需要的局部的contraints。
    2. layout,其从上向下(from super view to subview),此步主要应用上一步的信息去设置view的center和bounds。可以通过调用setNeedsLayout去触发此步骤,此方法不会立即应用layout。如果想要系统立即的更新layout,可以调用layoutIfNeeded。另外,自定义view可以重写方法layoutSubViews来在layout的工程中得到更多的定制化效果。
    3. display,此步时把view渲染到屏幕上,它与你是否使用Auto layout无关,其操作是从上向下(from super view to subview),通过调用setNeedsDisplay触发,

    因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。
    需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。
    注意:如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。

    相关文章

      网友评论

        本文标题:Masonry

        本文链接:https://www.haomeiwen.com/subject/tprwyxtx.html