从Autolayout到Masonry

目录

  • 一、Autolayout的使用
    对比了一下Autolayout和Masonry,突出Masonry代码的简洁性。

  • 二、链式编程
    从一个demo深入浅出的介绍了下链式编程思想(内容节选自iOS音视频

  • 三、Masonry源码解读
    3.1、View+MASAdditions
    3.2、MASConstraint
    ----- MASCompositeConstraint
    3.3、MASViewConstraint
    3.4、MASViewAttribute
    3.5、MASConstraintMaker
    3.6、NSArray+MASAdditions
    3.7、ViewController+MASAdditions

一、Autolayout的使用:

/**
 使用NSLayouConstraont为控件添加约束

 @param view1 需要添加约束的控件
 @param attr1 添加约束的方向
 @param relation 约束值关系(>、<、=)
 @param view2 被参照控件
 @param attr2 被参照控件的被参照方向
 @param multiplier 间距倍数关系
 @param c 传入最终差值
 @return NSLayoutConstraint
 */
+(instancetype)constraintWithItem:(id)view1
                        attribute:(NSLayoutAttribute)attr1
                        relatedBy:(NSLayoutRelation)relation
                           toItem:(nullable id)view2
                        attribute:(NSLayoutAttribute)attr2
                       multiplier:(CGFloat)multiplier
                         constant:(CGFloat)c
{
    /**
     *  NSLayoutAttribute枚举
     *
     *  NSLayoutAttributeLeft = 1,
     *  NSLayoutAttributeTop,
     *  NSLayoutAttributeBottom,
     *  NSLayoutAttributeLeading,
     *  NSLayoutAttributeTrailing,
     *
     *  ......
     *
     *  NSLayoutAttributeNotAnAttribute
     */
    
    
    /**
     *  NSLayoutRelation枚举
     *
     *  NSLayoutRelationLessThanOrEqual,      小于等于
     *  NSLayoutRelationEqual,                等于
     *  NSLayoutRelationGreaterThanOrEqual,   大于等于
     */
}

Autolayout的简单使用
1.1、如图所示,创建一个redview,距superView上、下、左、右各100pt.


demo-1.png
创建对象.png
NSLayoutConstraint.png

1.2、Masonry对比

masonry.png

二、链式编程

  • 将多个操作(多行代码)通过()链接在一起成为一句代码,使代码的可读性好。
  • 特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)。
  • 代表:masonry

关于链式编程,我在简书上发现了深入浅出-iOS函数式编程的实现 && 响应式编程概念这篇文章,讲的真如标题一样,深入浅出,非常便于刚开始接触链式编程思想的人了解。在这里将这个哥们的文章摘除一小段说一下。

链式编程经典语句

make.top.mas_equalTo(self.view).with.mas_equalTo(100);

如何书写链式编程
新建一个Person类,并为其增加两个方法

@interface Person : NSObject

- (void)run;

- (void)study;

@end

@implementation Person

- (void)run
{
    NSLog(@"run");
}

- (void)study
{
    NSLog(@"study");
}

@end

实例化并调用相关的方法

Person *person = [[Person alloc] init];
[person run];
[person study];

在这里我们实现了一个非常简单的对象方法调用,这是我们最常用的方法,而我们的最终目标是要写成这样,调用完成之后直接添加点语法继续调用。

person.runBlock().studyBlock().runBlock();

可以先将最终的目标进行分解,例如

[[person run] study];

分析:
显然,如果想要实现[person run]调用一个方法,那么run就需要一个返回一个对象,让这个对象去调用study。这样分析后,就简单了,就是增加一个返回值。

@interface Person : NSObject

- (Person *)run;

- (Person *)study;

@end
@implementation Person

- (Person *)run
{
    NSLog(@"run");
    return [[Person alloc] init];
}

- (Person *)study
{
    NSLog(@"study");
    return[[Person alloc] init];
}

@end

实现最终目标:

person.runBlock().studyBlock().runBlock();

在OC中,`()`block是以()的形式去执行的,猜想如果返回一个block的话,那么我就可以用()来实现runBlock()这种效果了吧!
再结合我们的分解步骤,runBlock()代表执行了一个block,如果这个block的返回值的是一个对象的话,那么调用另外一个方法;这样就可以一直链接下去吧!实现了我们想要的目标!

@interface Person : NSObject

- (Person* (^)(void))runBlock;

- (Person* (^)(void))studyBlock;

@end
@implementation Person

- (Person* (^)(void))runBlock
{
    Person *(^block)(void) = ^() {
        NSLog(@"run");
        return self;
    };
    return block;
}

- (Person* (^)(void))studyBlock
{
    Person *(^block)(void) = ^() {
        NSLog(@"study");
        return self;
    };
    return block;
}

@end

外部调用

Person *person = [[Person alloc] init];
person.runBlock().studyBlock();

masonry类比

make.top.mas_equalTo(self.view).with.mas_equalTo(100);

------------------------------------------------------------

- (MASConstraint * (^)(id))mas_equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

在这里,借用iOS音视频这个哥们的一个小demo,整理了一下链式编程的一个概念

  • 如果想再去调用别的方法,那么就需要返回一个对象;
  • 如果想用()去执行,那么需要返回一个block;
  • 如果想让返回的block再调用对象的方法,那么这个block就需要返回一个对象(即返回值为一个对象的block)

三、Masonry源码解读

Masonry目录.png

Masonry是iOS在控件布局中经常使用的一个轻量级框架,在日常开发中,你可能不知道Autolayout,但是你一定知道Masonry,它是对Autolayout进行了二次封装,用链式编程的思想简化了NSLayoutConstraint的使用方式,使代码变得更为简洁。

首先就从入口函数开始讲起


函数入口.png

3.1、View+MASAdditions
首先为view创建了一个分类,方便控件直接调用

// MASViewAttribute主要是将view与约束封装在了一起,在下面回说到
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left; //
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
......
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
......

// 创建约束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
// 更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
// 删除原来并创建最新的约束
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
// 寻找两个视图的最近的公共父视图(类比两个数字的最小公倍数)
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;

  • 方法解读 mas_makeConstraints
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block
{
    // 取消自动添加约束
    self.translatesAutoresizingMaskIntoConstraints = NO;

    // 创建MASConstraintMaker工厂类
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];

    // 通过block回调添加的约束,为constraintMaker的属性赋值
    block(constraintMaker);

    // 添加约束并返回约束数组(Array<MASConstraint>)
    return [constraintMaker install];
}

创建、更新、删除并更新约束的区别

由源代码可以看出
/**
 *  mas_updateConstraints
 *
 *  constraintMaker.updateExisting = YES;
 */

/**
 *  mas_remakeConstraints
 *
 *  constraintMaker.removeExisting = YES;
 */
- (NSArray *)install
{
    // 如果removeExisting属性为YES
    if (self.removeExisting)
    {
        // 遍历所有的约束,全部移除
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints)
        {
            [constraint uninstall];
        }
    }
    
    /**
     *   self.constraints
     *
     *   存放所有的约束
     *   NSArray<NSLayoutConstraint *> *constraints
     */
    NSArray *constraints = self.constraints.copy;
    
    // 约束安装
    for (MASConstraint *constraint in constraints)
    {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    
    // 清空constraints(将约束安装完成后,这个临时存放约束的容器就没用了)
    [self.constraints removeAllObjects];
    return constraints;
}

在以上方法里面需要解读的就是[constraint install],那我们就进入到MASConstraint这个类型来一探究竟。

3.2、MASConstraint、MASCompositeConstraint、MASViewConstraint

- (void)install { MASMethodNotImplemented(); }

// 卧槽,这是什么鬼,MASMethodNotImplemented定义了一个宏,大概意思就是说方法未实现,点进去看是这样:

#define MASMethodNotImplemented() \
    @throw [NSException exceptionWithName:NSInternalInconsistencyException \
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
                                 userInfo:nil]

抛出NSException的提示“一定要在子类中实现这个消息方法”,奥!!明白了,需要子类进行重写,那我们来看一下都有谁继承了MASConstraint类:

/**
 *    A group of MASConstraint objects
 *
 *    一组“MASConstraint”集合(存储一系列约束)
 */
@interface MASCompositeConstraint : MASConstraint


/**
 *  A single constraint.
 *  Contains the attributes neccessary for creating a NSLayoutConstraint and adding it to the appropriate view
 *
 *  该类是对NSLayoutConstraint的进一步封装
 */
@interface MASViewConstraint : MASConstraint <NSCopying>

从注释中我们可以了解到,“ MASCompositeConstraint”只是用来存储的容器,而“ MASViewConstraint”则是对“ NSLayoutConstraint”的进一步封装,所以,直接进入MASViewConstraint中查看被重写的“install”

3.3、MASViewConstraint (它的父类是MASViewConstraint)

- (void)install
{
    //1、检测该约束是否已经安装
    if (self.hasBeenInstalled) {
        return;
    }
    
    //2、从MASViewAttribute中分离view和约束
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
    
    //3、如果没有secondViewAttribute,默认secondItem为其父类,secondAttribute等于firstLayoutAttribute
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    
    //4、创建autolayout的约束
    MASLayoutConstraint *layoutConstraint
    = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                    attribute:firstLayoutAttribute
                                    relatedBy:self.layoutRelation
                                       toItem:secondLayoutItem
                                    attribute:secondLayoutAttribute
                                   multiplier:self.layoutMultiplier
                                     constant:self.layoutConstant];
    
    //5、将priority和key赋值
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    
    //6、找到要添加约束的installView
    if (self.secondViewAttribute.view) {
        MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        NSAssert(closestCommonSuperview,
                 @"couldn't find a common superview for %@ and %@",
                 self.firstViewAttribute.view, self.secondViewAttribute.view);
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        self.installedView = self.firstViewAttribute.view;
    } else {
        self.installedView = self.firstViewAttribute.view.superview;
    }
    
    // 7、添加或更新约束
    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

该类是对NSLayoutConstriant的进一步封装,作用:初始化NSLayoutConstriant并将该对象添加在相应的视图上。

- (id)copyWithZone:(NSZone __unused *)zone {
    MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
    constraint.layoutConstant = self.layoutConstant;
    constraint.layoutRelation = self.layoutRelation;
    constraint.layoutPriority = self.layoutPriority;
    constraint.layoutMultiplier = self.layoutMultiplier;
    constraint.delegate = self.delegate;
    return constraint;
}

在这里我们需要对以上代码进行解读:
1、install这个核心方法处于MASViewConstraint这个类中,在上面我们说过,该类是对NSLayoutConstraint的进一步封装,NSLayoutConstriant在初始化时需要NSLayoutAttribute和所约束的View,MASViewAttribute的类主要是将view和attributet融合在了一起。
2、secondViewAttribute来源
secondViewAttribute的值来自 “make.top.mas_equalTo(self.view)” 中的 “(self.view)”,这一点我们可以在以下方法中找到答案

mas_equalTo.png
MASConstraint.png
equalToWithRelation.png

3.4、MASViewAttribute 类解读
这个类相对来说比较简单,从类名可以看出这个类是对NSLayoutAttribute的封装(每一个Attribute都有一个View与之对应)

MASViewAttribute = UIView + NSLayoutAttribute + item,

view表示所约束的对象,而item就是该对象上可以被约束的部分
@interface MASViewAttribute : NSObject

@property (nonatomic, weak, readonly) MAS_VIEW *view;
@property (nonatomic, weak, readonly) id item;
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;

- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute;
- (BOOL)isSizeAttribute;

@end

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if (!self) return nil;
    
    _view = view;
    _item = item;
    _layoutAttribute = layoutAttribute;
    
    return self;
}

3.5、MASConstraintMaker(工厂类)
负责创建MASConstraint类型的对象,用户可以通过该Block回调过来的MASConstraintMaker对象给View指定添加的约束以及约束的值。该工厂中的constraints属性数组就记录了该工厂创建的所有MASConstraint对象。

在上面masonry的使用中,我们提到过这个类中的“install”方法,下面就一些其他属性做一下介绍。

@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;
......

除了上面这些方向性的约束,还有一些如size、edges、center都可以帮助我们更为方便简洁的使用masonry.
例如:

[redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.mas_equalTo(UIEdgeInsetsMake(10, 10, 10, 10));
}];

mas_makeContrains:
(1)、创建一个约束制造者
(2)、调用block(maker),把所有控件的约束全部保存到约束制造者里面
(3)、[constrainMaker install],遍历约束制造者的所有约束给控件添加约束

3.6、NSArray+MASAdditions
这个数组的分类主要提供了UI成组适配,这里就使用做一下介绍

// 组内方向
typedef NS_ENUM(NSUInteger, MASAxisType) {
    MASAxisTypeHorizontal,
    MASAxisTypeVertical
};

// 下面这三个方法与View+MASAdditions用法及效果相同,这里不做过多阐述
- (NSArray *)mas_makeConstraints:(void (^)(MASConstraintMaker *make))block;

- (NSArray *)mas_updateConstraints:(void (^)(MASConstraintMaker *make))block;

- (NSArray *)mas_remakeConstraints:(void (^)(MASConstraintMaker *make))block;

/**
 *  组间距固定,宽度距自适应
 *
 *  @param axisType     方向
 *  @param fixedSpacing 组间距
 *  @param leadSpacing  第一个view与父类开始的(左/上)距离
 *  @param tailSpacing  最后一个view与父类结束(右/下)的距离
 */
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
                    withFixedSpacing:(CGFloat)fixedSpacing
                         leadSpacing:(CGFloat)leadSpacing
                         tailSpacing:(CGFloat)tailSpacing;

/**
 *  宽度固定,组间距自适应
 *
 *  @param axisType        方向
 *  @param fixedItemLength 单个控件宽度
 *  @param leadSpacing     第一个view与父类开始的(左/上)距离
 *  @param tailSpacing     最后一个view与父类结束(右/下)的距离
 */
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
                 withFixedItemLength:(CGFloat)fixedItemLength
                         leadSpacing:(CGFloat)leadSpacing
                         tailSpacing:(CGFloat)tailSpacing;

例:对一组view进行约束添加

UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];

UIView *grayView = [[UIView alloc] init];
grayView.backgroundColor = [UIColor grayColor];
[self.view addSubview:grayView];

宽度固定,间距自适应

[@[redView, blueView, grayView] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
                                         withFixedItemLength:10
                                                 leadSpacing:30
                                                 tailSpacing:30];

[@[redView, blueView, grayView] mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(self.view.mas_top).with.offset(100);
    make.height.mas_equalTo(100);
}];

间距固定,宽度自适应

[@[redView, blueView, grayView] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
                                            withFixedSpacing:10
                                                 leadSpacing:30
                                                 tailSpacing:30];
[@[redView, blueView, grayView] mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(self.view.mas_top).with.offset(100);
    make.height.mas_equalTo(100);
}];

效果图如下:


NSArray+MASAdditions.png

3.7、ViewController+MASAdditions
提供了ViewController的LayoutGuide相关属性,自动根据bar高度设置的引导属性值。

UINavigationBar UITabbar
mas_topLayoutGuide mas_bottomLayoutGuide
mas_topLayoutGuideTop mas_bottomLayoutGuideTop
mas_topLayoutGuideBottom mas_bottomLayoutGuideBottom

例:
存在navigationBar 时,mas_topLayoutGuideBottom 相当于 增加了44。
不存在navigationBar 时,mas_topLayoutGuideBottom 相对于 0 。

简单使用:

[redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.right.mas_equalTo(self.view);
    make.top.mas_equalTo(self.mas_topLayoutGuide);
    make.height.mas_equalTo(100);
}];

END

第一次就源码解读分析写一篇博客,由于篇幅较长,对文章整体的把控可能较差,后续还会打磨一下,希望各位不吝赐教

参考文章:
Masonry 框架的使用
追求Masonry
深入浅出-iOS函数式编程的实现 && 响应式编程概念
Masonry - 自动布局

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容