Masonry

Masonry是一个轻量级的布局框架,但是代码中使用了大量的block和链式编程的技巧,所以下面简单介绍链接语法以及block。

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是获取了属性
    1. 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;   //文本基线

常见约束的各种类型

描述 key
尺寸 width、height、size
边界 left、leading、right、trailing、top、bottom
中心点 center、centerX、centerY
边界 edges
偏移量 offset、insets、sizeOffset、centerOffset
约束优先级 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。
  • 对一个数组进行约束
/**
     *  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属性为false,block内部已经做过处理了;
防止block中的循环引用,使用弱引用(这是错误观点),在这里block属于非逃逸闭包,block内部引用self不会造成循环引用的

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

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

  • 苹果官方建议:添加/更新约束在updateConstraints方法内,需要在最后调用[super 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)