源码阅读:Masonry(九)—— NSArray+MASAdditions/其他分类

691 阅读6分钟

该文章阅读的 Masonry 的版本为 1.1.0。

这个分类提供了为视图数组中的每一个视图设置约束的便捷方法。

1.公共枚举

typedef NS_ENUM(NSUInteger, MASAxisType) {
    MASAxisTypeHorizontal,
    MASAxisTypeVertical
};

这个枚举定义了水平和垂直两个方向。

2.公共方法

/**
 为视图数组中每一个视图添加并安装约束的方法
 */
- (NSArray *)mas_makeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
 为视图数组中每一个视图更新已安装约束的方法
 */
- (NSArray *)mas_updateConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
 为视图数组中每一个视图卸载已安装的约束并重新添加并安装约束的方法
 */
- (NSArray *)mas_remakeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
 使视图数组中的视图元素在水平或者垂直的方向以固定间距的方式进行排列的方法
 */
- (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;

3.实现

3.1 私有方法

/**
 该方法用于获取视图数组中所有视图元素的公共父视图
 */
- (MAS_VIEW *)mas_commonSuperviewOfViews
{
    // 创建变量保存公共父视图
    MAS_VIEW *commonSuperview = nil;
    // 创建变量保存前一个视图
    MAS_VIEW *previousView = nil;
    // 遍历当前数组中每一个视图元素
    for (id object in self) {
        // 数组中的元素必须是 UIView 及其子类
        if ([object isKindOfClass:[MAS_VIEW class]]) {
            // 创建变量保存当前视图
            MAS_VIEW *view = (MAS_VIEW *)object;
            if (previousView) {
                // 如果这不是第一个元素,那获取当前视图和前一个视图的公共父视图
                commonSuperview = [view mas_closestCommonSuperview:commonSuperview];
            } else {
                // 如果这是第一个元素,那公共父视图就是当前视图
                commonSuperview = view;
            }
            // 保存当前视图,用于下次循环
            previousView = view;
        }
    }
    NSAssert(commonSuperview, @"Can't constrain views that do not share a common superview. Make sure that all the views in this array have been added into the same view hierarchy.");
    // 返回公共父视图
    return commonSuperview;
}

3.2 公共方法

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block {
    NSMutableArray *constraints = [NSMutableArray array];
    for (MAS_VIEW *view in self) {
        NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
        [constraints addObjectsFromArray:[view mas_makeConstraints:block]];
    }
    return constraints;
}

- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block {
    NSMutableArray *constraints = [NSMutableArray array];
    for (MAS_VIEW *view in self) {
        NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
        [constraints addObjectsFromArray:[view mas_updateConstraints:block]];
    }
    return constraints;
}

- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
    NSMutableArray *constraints = [NSMutableArray array];
    for (MAS_VIEW *view in self) {
        NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
        [constraints addObjectsFromArray:[view mas_remakeConstraints:block]];
    }
    return constraints;
}

这三个方法都是遍历数组,获取数组中的视图元素,然后调用视图元素各自的分类方法设置约束,最后返回约束对象数组。


- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
    // 必须有 1 个以上的视图元素
    if (self.count < 2) {
        NSAssert(self.count>1,@"views to distribute need to bigger than one");
        return;
    }
    
    // 获取这些视图的公共父视图
    MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
    if (axisType == MASAxisTypeHorizontal) {
        // 如果设置的是水平方向布局
        // 创建变量保存前一个视图
        MAS_VIEW *prev;
        // 遍历视图数组
        for (int i = 0; i < self.count; i++) {
            // 获取视图元素
            MAS_VIEW *v = self[i];
            // 添加并安装约束的方法
            [v mas_makeConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    // 如果当前视图不是第一个视图
                    // 当前视图宽度等于前一个视图宽度
                    make.width.equalTo(prev);
                    // 当前视图左边距离前一个视图右边指定的间距
                    make.left.equalTo(prev.mas_right).offset(fixedSpacing);
                    if (i == self.count - 1) {
                        // 如果是最后一个视图
                        // 当前视图的右边距离公共父视图右边指定的尾距
                      make.right.equalTo(tempSuperView).offset(-tailSpacing);
                    }
                }
                else {
                    // 如果当前视图是第一个视图
                    // 当前视图的左边距离公共父视图左边指定的首距
                    make.left.equalTo(tempSuperView).offset(leadSpacing);
                }
                
            }];
            // 保存当前视图,用于下次循环
            prev = v;
        }
    }
    else {
        // 如果设置的是垂直方向布局
        // 创建变量保存前一个视图
        MAS_VIEW *prev;
        // 遍历视图数组
        for (int i = 0; i < self.count; i++) {
            // 获取视图元素
            MAS_VIEW *v = self[i];
            // 添加并安装约束的方法
            [v mas_makeConstraints:^(MASConstraintMaker *make) {
                if (prev) {
                    // 如果当前视图不是第一个视图
                    // 当前视图高度等于前一个视图高度
                    make.height.equalTo(prev);
                    // 当前视图上边距离前一个视图上边指定的间距
                    make.top.equalTo(prev.mas_bottom).offset(fixedSpacing);
                    if (i == self.count - 1) {
                        // 如果是最后一个视图
                        // 当前视图的下边距离公共父视图下边指定的尾距
                        make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
                    }                    
                }
                else {
                    // 如果当前视图是第一个视图
                    // 当前视图的上边距离公共父视图上边指定的首距
                    make.top.equalTo(tempSuperView).offset(leadSpacing);
                }
                
            }];
            // 保存当前视图,用于下次循环
            prev = v;
        }
    }
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
    // 必须有 1 个以上的视图元素
    if (self.count < 2) {
        NSAssert(self.count>1,@"views to distribute need to bigger than one");
        return;
    }
    
    // 获取这些视图的公共父视图
    MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
    if (axisType == MASAxisTypeHorizontal) {
        // 如果设置的是水平方向布局
        // 创建变量保存前一个视图
        MAS_VIEW *prev;
         // 遍历视图数组
        for (int i = 0; i < self.count; i++) {
             // 获取当前视图元素
            MAS_VIEW *v = self[i];
            // 添加并安装约束的方法
            [v mas_makeConstraints:^(MASConstraintMaker *make) {
                // 当前视图的宽度等于指定的宽度
                make.width.equalTo(@(fixedItemLength));
                if (prev) {
                    // 如果当前视图不是第一个视图
                    if (i == self.count - 1) {
                        // 如果是最后一个视图
                        // 当前视图的右边距离公共父视图右边指定的尾距
                        make.right.equalTo(tempSuperView).offset(-tailSpacing);
                    }
                    else {
                        // 如果不是最后一个视图
                        // 计算偏移量
                        CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
                        // 当前视图的右边距离公共父视图的右边计算出的大小
                        make.right.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
                    }
                }
                else {
                    // 如果当前视图是第一个视图
                    // 当前视图的左边距离公共父视图左边指定的首距
                    make.left.equalTo(tempSuperView).offset(leadSpacing);
                }
            }];
            // 保存当前视图,用于下次循环
            prev = v;
        }
    }
    else {
        // 如果设置的是垂直方向布局
        // 创建变量保存前一个视图
        MAS_VIEW *prev;
        // 遍历视图数组
        for (int i = 0; i < self.count; i++) {
            // 获取当前视图元素
            MAS_VIEW *v = self[i];
            // 添加并安装约束的方法
            [v mas_makeConstraints:^(MASConstraintMaker *make) {
                // 当前视图的高度等于指定的高度
                make.height.equalTo(@(fixedItemLength));
                if (prev) {
                    // 如果当前视图不是第一个视图
                    if (i == self.count - 1) {
                        // 如果是最后一个视图
                        // 当前视图的底边距离公共父视图底边指定的尾距
                        make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
                    }
                    else {
                        // 如果不是最后一个视图
                        // 计算偏移量
                        CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
                        // 当前视图的底边距离公共父视图的底边计算出的大小
                        make.bottom.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
                    }
                }
                else {
                    // 如果当前视图是第一个视图
                    // 当前视图的顶边距离公共父视图顶边指定的首距
                    make.top.equalTo(tempSuperView).offset(leadSpacing);
                }
            }];
            // 保存当前视图,用于下次循环
            prev = v;
        }
    }
}

4.其他分类

剩下的几个类都比较简单,所以就在一篇里写了。

4.1 ViewController+MASAdditions

这个分类中提供的方法是为了方便创建 UIViewControllertopLayoutGuidebottomLayoutGuide 相关的视图属性封装对象。

4.2 NSLayoutConstraint+MASDebugAdditions

该分类中没有提供什么方法,只是重写了 NSLayoutConstraint 类的 description 方法,目的是为了我们在调试约束冲突的时候,使控制台打印出的内容可读性更强。

4.3 View+MASShorthandAdditions

想要使用这个分类提供的方法,得在 #import "Masonry.h" 之前先定义一个宏 MAS_SHORTHAND,这样就可以省略 mas_ 前缀。

4.4 NSArray+MASShorthandAdditions

这个分类的作用和上一个分类的作用是相同的,都是为了省略前缀 mas_