使用masonry的一个坑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用masonry的一个坑相关的知识,希望对你有一定的参考价值。

参考技术A 示例:
有一个 tableview ,上面有 cell , cell 上面有一个 UILabel ,这个 UILabel 的高度 height 是由数据源中计算得来,那么在处理数据源的方法

中做这个 UILabel 的约束时如果采用相对约束:

则没有问题。

如果采用绝对约束:即直接设置高度,而不是设置底部对齐:

这样由于 cell 的复用性,在下一次复用这个 cell 的时候, masonry 不会再去重新设置这个 height ,但是 height 又由于数据源不一样而需要改变,就导致了显示错误。此时将 mas_makeConstraints 的方式改为 mas_remakeConstraints 的方式即可:

Masonry 源码简单解析

Masonry是一个轻量级的用于自动布局的框架,是对系统的自动布局约束的一个封装。
Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式,让我们可以以链式的方式为我们的控件指定约束。


上面是Masonry的类图,从类图中我们来整体的分析Masonry框架的结构。然后再由整体到部分逐渐的细化,窥探其内部的实现细节。
masonry框架的类结构
根据上面的类图,我们分别看一下框架里面各个类。
1、UIView的分类 - View+MASAdditions上面红线框住的部分

上图是View+MASAdditions文件中,为UIView添加了类型为MASViewAttribute的成员属性,以mas_left成员属性为例,因为MASViewAttribute是View与NSLayoutAttribute的合体,所以mas_left就代表着当前View的NSLayoutAttributeLeft属性,也就是mas_left存储的是当前View的NSLayoutAttributeLeft属性。
同理,mas_top就代表着当前View的NSLayoutAttributeTop属性,其他成员属性也是一样。
除了添加一系列的成员属性外,还添加了四个公有的方法:mas_closestCommonSuperview方法负责寻找两个视图的最近的公共父视图(类比两个数字的最小公倍数)、mas_makeConstraints方法负责创建安装约束、mas_updateConstraints负责更新已经存在的约束(若约束不存在就Install)、mas_remakeConstraints方法则负责移除原来已经创建的约束并添加上新的约束。上述方式是UIView对象设置约束主要调用的方法,稍后会详细介绍其实现方式。

/**
 *	Finds the closest common superview between this view and another view
 *
 *	@param	view	other view
 *
 *	@return	returns nil if common superview could not be found
 */
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
这个方法负责寻找两个视图的最近的公共父视图。
它的实现方法:

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view 
    MAS_VIEW *closestCommonSuperview = nil;
    MAS_VIEW *secondViewSuperview = view;
    while (!closestCommonSuperview && secondViewSuperview) 
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) 
            if (secondViewSuperview == firstViewSuperview) 
                closestCommonSuperview = secondViewSuperview;
            
            firstViewSuperview = firstViewSuperview.superview;
        
        secondViewSuperview = secondViewSuperview.superview;
    
    return closestCommonSuperview;

其中MAS_VIEW是宏,就是UIView。上面的两个while循环中通过遍历两个view的superview,在内部while中判断superview是否相等,进而找出了最近的公共父视图。

/**
 *  Creates a MASConstraintMaker with the callee view.
 *  Any constraints defined are added to the view or the appropriate superview once the block has finished executing
 *
 *  @param block scope within which you can build up the constraints which you wish to apply to the view.
 *
 *  @return Array of created MASConstraints
 */
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

这个方法负责创建约束,是不是很熟悉,就是我们外部调用的
[self.view mas_makeConstraints:^(MASConstraintMaker *make)
//约束们
];
mas_makeConstraints的方法实现中

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block 
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];

首页禁用Autoresizing,使用Autolyout自动布局,创建一个约束制造者constraintMaker,查看MASConstraintMaker中的
initWithView:(MAS_VIEW *)view
的实现

- (id)initWithView:(MAS_VIEW *)view 
    self = [super init];
    if (!self) return nil;
    
    self.view = view;
    self.constraints = NSMutableArray.new;
  
    return self;

将view赋值给maker的属性view,另外我们发现

maker的view属性是weak的

  • (这是重点,说明maker没有强引用view,也就是说mas_makeConstraints实现中,block(constraintMaker),对constraintMaker中的MAConstraint类型的属性进行初始化,block
    没有强引用view那么这就是我们在外部使用mas_makeConstraints时,不使用weakself的原因了)

,属性constraints是用来保存约束们的数组。

不使用weakself的原因
这个block只是个栈block,而且构不成循环引用的条件。栈block有个特性就是它执行完毕之后就出栈,出栈了就会被释放掉。看mas_makexxx的方法实现会发现这个block很快就被调用了,完事儿就出栈销毁,构不成循环引用,所以可以直接放心的使用self。
https://blog.csdn.net/blackeynes/article/details/78853109

/**
 *  Creates a MASConstraintMaker with the callee view.
 *  Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
 *  If an existing constraint exists then it will be updated instead.
 *
 *  @param block scope within which you can build up the constraints which you wish to apply to the view.
 *
 *  @return Array of created/updated MASConstraints
 */
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

负责更新已经存在的约束(若约束不存在就Install)

/

**
 *  Creates a MASConstraintMaker with the callee view.
 *  Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
 *  All constraints previously installed for the view will be removed.
 *
 *  @param block scope within which you can build up the constraints which you wish to apply to the view.
 *
 *  @return Array of created/updated MASConstraints
 */
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

责移除原来已经创建的约束并添加上新的约束
③④这两个函数内部的实现与mas_makeConstraints类似,就是多了一个属性的设置。mas_updateConstraints中将constraintMaker中的updateExisting设置为YES, 也就是说当添加约束时要先检查约束是否已经被安装了,如果被添加了就更新,如果没有被添加就添加。而mas_remakeConstraints中所做的事情是将removeExisting属性设置成YES, 表示将当前视图上的旧约束进行移除,然后添加上新的约束

2、工厂类MASConstraintMaker 类图中绿色框住的部分
该类就是一个工厂类,负责创建MASConstraint类型的对象(依赖于MASConstraint接口,而不依赖于具体实现)。在UIView的View+MASAdditions类目中就是调用的MASConstraintMaker类中的一些方法。上述我们在使用Masonry给subView添加约束时,mas_makeConstraints方法中的Block的参数就是MASConstraintMaker的对象。用户可以通过该Block回调过来的MASConstraintMaker对象给View指定要添加的约束以及该约束的值。该工厂中的constraints属性数组就记录了该工厂创建的所有MASConstraint对象。

上图是MASConstraintMaker中的部分属性,可以看出这些属性都是MSAConstrian类型

我们查看MSAConstrian类,上面的意思是MSAConstrian可以是约束们通过链式编程的方式创建出来
约束可以表示一个NSLayoutConstraint(苹果原生约束类),MASViewConstraint是MASConstraint的子类

也就是说MASViewConstraint相当于iOS中的NSLayoutConstraint
在maker中

上图是部分属性的getter方法,都调用了

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];


- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute

是MASConstraint的私有方法通过代理实现,

在maker的代理实现

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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;
    
    if (!constraint) 
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    
    return newConstraint;

用来添加约束,并且上面方法的返回值都是MASConstraint
这样我们就可以通过链式编程创建约束们了。这样我们就可以通过[constraintMaker install]来更新约束了。

以上是关于使用masonry的一个坑的主要内容,如果未能解决你的问题,请参考以下文章

iOS知识汇使用masonry的一个坑

《Vue插件》瀑布流插件vue-masonry的使用与踩坑记录

autolayout

IQKeyboardManager的一些坑

Masonry简单使用教程

Masonry 源码简单解析