Masonry框架浅析-链式编程

作者: 过过过客 | 来源:发表于2016-07-07 08:55 被阅读626次
    • 首先我们来导入Masonry看下效果:
    #import "ViewController.h"
    #import "Masonry.h"
    
    
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 添加一个黄色的view
        [self addYellowView];
        
    }
    
    
    
    - (void)addYellowView {
        
        UIView *yellowView = [[UIView alloc]init];
        
        yellowView.backgroundColor = [UIColor yellowColor];
        
        [self.view addSubview:yellowView];
        
        // 设置约束
        [yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
            
            // 设置顶部的约束 距self.view顶部为100
            make.top.equalTo(self.view).offset(100);
            
            // 设置左边的约束
            make.left.equalTo(self.view).offset(20);
            
            // 设置右边的约束
            make.right.equalTo(self.view).offset(-20);
            
            // 设置高
            make.height.equalTo(@50);
            
        }];
    
    }
    

    运行的效果:


    Snip20160707_1.png

    于是乎,通过这个框架,我们在给UIButton设置一些属性的时候可以这样做:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 添加一个按钮
        [self addButton];
        
    }
    
    - (void)addButton {
        // 创建按钮
        UIButton *button = [[UIButton alloc]init];
        
        // 设置frame
        button.frame = CGRectMake(50, 100, 150, 50);
        
        // 绑定了点击事件
        [button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
        
        // 设置属性
        [button lj_makeAttribute:^(LJButtonManager *make) {
            
            // 设置普通状态的图片 背景图片 文字颜色 文字内容
            make.normal.imgName(@"LJbtn_img_normal").bgImgName(@"LJbtn_bgImg_normal").color([UIColor redColor]).title(@"我是普通状态");
            
            // 设置选中状态的图片 背景图片 文字颜色 文字内容
            make.selected.imgName(@"LJbtn_img_selectedl").bgImgName(@"LJbtn_bgImg_selected").color([UIColor blueColor]).title(@"我是选中状态");
        }];
        // 添加
        [self.view addSubview:button];
    }
    
    - (void)buttonAction:(UIButton *)sender {
        // 切换按钮状态
        sender.selected = !sender.selected;
        
    }
    

    我们来看下运行之后的效果:
    Normal状态下:

    Snip20160707_2.png

    Selected状态下:

    Snip20160707_3.png

    怎么样,是不是很爽,有时需要给button多个不同状态设置属性,可以这样点 点 点(.image.bgImage.color.title.frame) 想点什么,自己就往里面加什么方法, 是不是很爽

    那这个是怎么实现的呢?

    • 首先我们来看一下masonry怎么实现的:
        UIView *yellowView = [[UIView alloc]init];
        
        yellowView.backgroundColor = [UIColor yellowColor];
        
        [self.view addSubview:yellowView];
        
        // 设置约束
        [yellowView mas_makeConstraints:^(MASConstraintMaker *make) {
            
            // 设置顶部的约束 距self.view顶部为100
            make.top.equalTo(self.view).offset(100);
            
            // 设置左边的约束
            make.left.equalTo(self.view).offset(20);
            
            // 设置右边的约束
            make.right.equalTo(self.view).offset(-20);
            
            // 设置高
            make.height.equalTo(@50);
            
        }];
    

    我们com + 左键mas_makeConstraints: 到这个方法里面去看一下

    // UIView 的分类
    @implementation MAS_VIEW (MASAdditions)
    
    /**
     *  添加约束的方法
     *
     *  @param block 无返回值 参数为 约束管理者对象的 block
     *
     *  @return 存有所有约束的数组
     */
    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
        // 将view自带的约束设置为NO 避免冲突
        self.translatesAutoresizingMaskIntoConstraints = NO;
        // 创建约束管理者 并将 self 传进去   此时的self是 当前方法的调用者
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        // 调用传进来的的block
        block(constraintMaker);
        // 返回存有所有约束行为的数组
        return [constraintMaker install];
    }
    
    • 这个类是UIView的一个分类

      • 我们的view在调用mas_makeConstraints:这个方法的时候,需要传递一个 无返回值,参数为MASConstraintMaker对象的block
        我们在调用这个方法的时候,需要传入这样子的一个block,并且给这个block赋值,赋值的过程就相当于我们在给view设置约束
    • 这个是怎么设置约束的呢

      • make.top.equalTo(self.view).offset(100);我们来看下这行代码,这行代码里面,这行代码里面,是通过make这个对象设置约束的,make.top表示给顶部添加约束

      • 那现在我们com + 左键到这个MASConstraintMaker里面看下:

    @interface MASConstraintMaker : NSObject
    
    /**
     *  The following properties return a new MASViewConstraint
     *  with the first item set to the makers associated view and the appropriate MASViewAttribute
     */
    @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;
    

    这些就是我们需要view的那些位置设置约束,但是我们可以看到@property (nonatomic, strong, readonly) MASConstraint *top; 这个top位置属性是一个MASConstraint的对象,来到MASConstraintMaker.m文件

    我们找到了这个top属性的get方法:

    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        
        // 执行添加约束的方法
        return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
    }
    
    - (MASConstraint *)left {
        return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
    }
    
    - (MASConstraint *)top {
        // 给view添加一个顶部位置的约束 返回值为方法调用者本身 这样又可以接着调用方法
        return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
    }
    

    发现这个方法最重执行的是[self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute]这个方法,继续com + 左键进去,我们来到这个方法的实现:

    - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        // 创建一个约束
        MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
        // 创建一个具体的约束的管理者 并把约束传过去
        MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
        // 因为传过来的nil 所以不执行
        if ([constraint isKindOfClass:MASViewConstraint.class]) {
            //replace with composite constraint
            NSArray *children = @[constraint, newConstraint];
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self;
            [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        }
        // 传过来的为nil 执行此方法
        if (!constraint) {
            // 设置代理
            newConstraint.delegate = self;
            // 将约束添加到存有约束的数组
            [self.constraints addObject:newConstraint];
        }
        // 返回具体的约束管理者
        return newConstraint;
    }
    

    我们可以看到这里面就是在进行设置约束的一些操作 ,最后的返回值是MASViewConstraint对象,至此,我们大概可以认为已经确定了要给viewtop设置约束,并且返回值是一个MASViewConstraint对象

    • 我们接着看这行代码make.top.equalTo(self.view).offset(100);,make.top是确定了给view的哪个位置设置约束,我们在来看看make.top.equalTo(self.view)这行代码,还是一样,com + 左键equalTo(self.view)里面:
    /**
     *  返回值为: 返回值为 MASConstraint对象 参数为 id类型 的一个block
     */
    - (MASConstraint * (^)(id))equalTo {
        
        // 返回一个block
        return ^id(id attribute) {
            
            // 给 view 的top 设置相对于 attribute 设置约束
            // 此时的 attribute 就是我们 make.top.equalTo(self.view).offset(100); 中的self.view
            return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
        };
    }
    

    到这里,我们基本上就可以认为make.top.equalTo(self.view)这行代码执行就可以让 yellowViewtopself.viewtop0了(默认是0)

    • 接着,我们继续看make.top.equalTo(self.view).offset(100);,还是一样com + 左键offset(100):
    /**
     *  返回值为: 返回值是 MASConstraint对象 ,参数是 CGFloat类型的 一个block
     */
    - (MASConstraint * (^)(CGFloat))offset {
        // 返回block
        return ^id(CGFloat offset){
            // 设置偏移值
            self.offset = offset;
            // block的返回值  返回自己
            return self;
        };
    }
    

    到这里,make.top.equalTo(self.view).offset(100);这行代码就执行完了,这个里面还有很多步骤,由于Masonry的作者封装的比较狠,理解起来困难还是大大的, 我这里也只是简单的介绍了一下这行代码的执行思路

    • 最后我们总结一下

      • 为什么make.top.equalTo(self.view).offset(100); 可以这样子一直点点点 ;make.top相当于get方法,这个方法的返回值的对象本身- (MASConstraint *)top

      我们也可以写成这样:

              MASConstraint *make1 = make.top;
            
            MASConstraint *make2 = make1.equalTo(self.view);
            
            MASConstraint *make3 = make2.offset(100);
      

      接着每次调用get方法之后,我又可以拿到调用者本身,于是我们又可以接着调方法,就可以一直点点点了;
      然后就是make.top.equalTo(self.view).offset(100);这个括号里面的参数,.offset的返回值是一个MASConstraint * (^)(CGFloat)block; 我们在执行了make.top.equalTo(self.view).offset之后,就可以拿到这个block;可以写成这样:

              // 定义block
            MASConstraint *(^block)(CGFloat);
            
            // 拿到返回回来的block
            block = make.top.equalTo(self.view).offset;
            
            // 调用block
            block(100);
            
            // 拿到block的返回值
            MASConstraint *make = block(100);
      

      block作为参数的时候,这个block是由外部来实现,内部调用的

      block作为返回值的时候,这个block是由内部来实现,外部调用的

    可能大家看到这里还是很多不明白,大家可以下一下我的demo,demo写的非常简单,你们下载之后,根据自己的理解,可以自己添加方法(比如.frame().titleEdgeInsets()(buttonframelabel缩进)),我也写了很多注释,相信能帮到你们,然后对demo有什么疑问的地方,或者有什么好的建议,希望大家联系我,共同探讨
    写到这里,我也要结束装逼了,大家一起装逼,才是真的装逼
    buttondemo的github地址:https://github.com/2098187liujing/-demo
    ending

    相关文章

      网友评论

      • 0caea7bd0799:很赞
        过过过客: @cxx3344 谢谢
      • 阿龍飛:希望以后多多交流
        过过过客:@阿龍飛 嗯
      • 阿龍飛: make.top.equalTo(self.view).offset(100);
        self 不是要弱引用么 __weak typeof(self)weakSelf = self;
        阿龍飛:谢谢你的指点,现在明白了 :smile:
        过过过客:这里纠正一下,make.top 返回的是 MASConstraint对象
        过过过客:@阿龍飛 有没有产生循环引用,主要是看控制器在销毁的时候,有没有走dealloc方法,我的demo里面有测试,是有走dealocl方法的,然后 make.top.equalTo(self.view).offset(100); 这里的make.top.equalTo ,这里的 make.top 返回的还是make这个对象 , make.top.equalTo 这里的 equalTo 是一个方法, 不是 make对象 的属性, 所以 make 并不会对 equalTo 产生强引用.
        当控制销毁的时候, yellowView会被释放, 这个make对象,没有强指针指向,也会被释放, 所以这里也就不会有强指针指向 self , 这里就不会存在循环引用了,当然,我们平时在使用block的时候,一般都会用week修饰,这样子比较保险, 你可以看下我的demo,我那个里面也没有使用weak的
      • 古斯比德: :+1: :clap:
        过过过客:@古斯比德 谢谢
      • Eugene_iOS::+1:
        过过过客:@Eugene_iOS 谢谢

      本文标题:Masonry框架浅析-链式编程

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