从Autolayout到Masonry

作者: 强子ly | 来源:发表于2018-04-10 09:17 被阅读227次

    目录

    • 一、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 - 自动布局

    相关文章

      网友评论

      • 舒马赫:Masory写的很棒,但是不喜欢纯代码写界面,太慢了。推荐使用xml的布局库FlexLib,采用前端布局标准flexbox(不使用autolayout),支持热刷新,国际化等。可以到这里了解详细信息:

        https://github.com/zhenglibao/FlexLib

      本文标题:从Autolayout到Masonry

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