Masonry 框架小结

2024-06-06 12:58
文章标签 框架 小结 masonry

本文主要是介绍Masonry 框架小结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Masonry 框架小结

自从 iOS 6.0 苹果引入自动布局以来,经过不断的发展,其已经成为我们进行 UI 布局的主要技术之一。在 Autolayout 之前,可以使用 Autoresizing 来约束视图的父子关系,但是局限性很大,很多任务无法实现,如其不能约束兄弟视图之间的关系。

相比之下,Autolayout 的功能则强大的多,其核心概念便是参照约束

NSLayoutConstraint

自动布局的关键是创造完整的约束,并将约束添加到正确的视图中。

NSLayoutConstraint 类是对约束的描述,其核心概念可以用一个公式表示,如下:

firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant

对于视图自身大小尺寸的约束而言,是不需要 secondItem 参照物的。不过,如果将此情况视为以自身为参照,那么就可以将约束理解为总是参照其他视图而存在的,即约束是相对而言的。

该类提供了两个类方法用来创建约束实例:

+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary<NSString *,id> *)metrics views:(NSDictionary<NSString *, id> *)views;

第一个方法,基本上就是对核心公式的赋值。而第二个方法,则是使用 VFL(Visual Format Language) 字符串来创建约束。

要准确快速的使用约束,需要了解 NSLayoutAttributeNSLayoutRelationUILayoutPriority 的可选值。

对于 NSLayoutAttribute 而言,需要注意的是 NSLayoutAttributeLeftNSLayoutAttributeRight 总是表示视图的左、右两侧,但是 NSLayoutAttributeLeadingNSLayoutAttributeTrailing 却不总是表示视图的左、右两侧,其表示的含义同当地视图排版习惯相关。所以,其表示的含义可能是左、右两侧,也可能是右、左两侧。

NSLayoutAttribute***Margin 表示的参照位置总是同 UIViewlayoutMargins 属性相关。

创建的约束要添加到视图中才会生效,但是需要将 UIViewtranslatesAutoresizingMaskIntoConstraints 属性置为 NO,并且约束相关联的视图必须都添加到视图层级中,而且约束需要添加到相关视图的共同父视图中(将视图本身看作其本身的父视图,即对于有包含关系的相关视图,约束要添加到层级高的视图中,而只涉及自身的约束,则添加到其自身即可)。

约束的使用的确减少了视图的布局和适配工作,但是直接使用 NSLayoutConstraint 类定义约束是十分繁琐的,并且代码会变得十分臃肿,不易理解、查错和维护,所以第三方框架的使用必不可少。

Masonry

Masonry 框架十分易用,支持链式编程,其实现并不复杂,但是设计的十分巧妙。理解 Masonry 中约束的实现过程,可以从约束的核心公式入手。

MASViewAttribute

MASViewAttribute 类,可以理解为对 NSLayoutAttribute 的封装,其除了保存布局时要参照的位置外,还持有了具体的布局对象和参照对象。

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

可见,布局对象是 UIView 对象,而参照对象则未必。并且,两者都是弱引用。

MASLayoutConstraint

MASLayoutConstraint 只是简单继承了 NSLayoutConstraint 类,可以预见,其是该框架中描述约束的类。另外,其定义了一个属性以便于调试,通过该属性,可以得知一个约束是否是通过该框架创建的。

@property (nonatomic, strong) id mas_key;

MASConstraint

MASConstraint 虽然是以 Constraint 结尾,但是其是 NSObject 的子类。

该类的巧妙设计使得 Masonry 框架中的方法可以链式调用,对于有参数的方法,无法使用点语法进行调用,所以在该类中通过返回包含参数的 block 来进行参数的传递。

可以将该类中的方法分为三类:

  1. 返回 block 的方法

    如下两个方法,分别表示对偏移量和约束属性的修改:

    - (MASConstraint * (^)(CGFloat))offset {return ^id(CGFloat offset){self.offset = offset;return self;};
    }- (MASConstraint * (^)(id))equalTo {return ^id(id attribute) {return self.equalToWithRelation(attribute, NSLayoutRelationEqual);};
    }	
    

    如同第一个方法,在返回的 block 中会使用调用时随后的指定参数对变量进行赋值,而对于类似第二个方法的对参照关系属性的修改,同样是在 block 中实现的。而其所使用的方法,在本类中实际是抽象方法,需要子类去具体实现。

  2. 返回本类实例的方法

    诸如 leftrighttopbottom 等方法,实际是返回了一个 MASConstraint 实例对象,从而可以进一步调用上面所说的第一种类型的方法。

    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {MASMethodNotImplemented();
    }- (MASConstraint *)left {return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
    }
    

    这些方法,实际都是对 addConstraintWithLayoutAttribute: 方法的调用,而其也是由子类实现的。

  3. 抽象方法

    该类中定义了诸多抽象方法,由子类来实现。

从上面的分析可知,该类的主要功能就是实现链式编程,而具体的约束创建则是由子类实现的。主要关注 addConstraintWithLayoutAttribute:equalToWithRelation 方法。

MASViewConstraint

MASViewConstraint 作为 MASConstraint 的子类,除了继承父类的相关属性外,还定义了 firstViewAttributesecondViewAttribute 两个属性,其实际就是约束相关的两个参照。

除了一个初始化方法外,还提供了一个类方法,用来获取所有通过该框架添加到一个视图中的约束。其是通过运行时函数,为 UIView 对象添加了一个属性 mas_installedConstraints 来保存所有通过该框架添加到该视图中的约束。

+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view {return [view.mas_installedConstraints allObjects];
}

虽然 MASViewConstraint 并不直接描述约束,但是其实际上包含了所有创建约束的条件,并且也仅仅表示一个约束。该类中定义了一个 NSLayoutConstraint 实例来保存该类的实例对象生成的具体约束,并且这个约束生效后,该实例对象会被添加到 mas_installedConstraints 属性中。不过,这个过程并不是自动的,而是需要调用 install 方法。

在 iOS 8.0 后,约束支持 active 属性,可以通过设置该属性的值来取代向视图中添加约束的操作。

if ([self supportsActiveProperty] && self.layoutConstraint) {self.layoutConstraint.active = YES;[self.firstViewAttribute.view.mas_installedConstraints addObject:self];return;
}

但是,self.layoutConstraint 第一次创建,或者不支持 active 时,仍然需要执行下面的操作。

if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {secondLayoutItem = self.firstViewAttribute.view.superview;secondLayoutAttribute = firstLayoutAttribute;
}

增加一个判断,当不是对视图自身尺寸的约束,而又没有参照时,则默认参照父视图。

接着,使用相关属性创建约束 MASLayoutConstraint 实例。

然后,获取应该添加该约束的视图,如果要求更新与之相同的约束,那么先遍历所有约束,找到与之相同的约束,直接更新即可,否则,将该约束添加到视图中,所以每个约束生效之前不会主动进行查重,即可能存在重复的约束。

该类中重要的是重写的两个方法 addConstraintWithLayoutAttribute:equalToWithRelation

- (MASConstraint *)left {return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint * (^)(id))equalTo {return ^id(id attribute) {return self.equalToWithRelation(attribute, NSLayoutRelationEqual);};
}	- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {return ^id(id attribute, NSLayoutRelation relation) {if ([attribute isKindOfClass:NSArray.class]) {NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");NSMutableArray *children = NSMutableArray.new;for (id attr in attribute) {MASViewConstraint *viewConstraint = [self copy];viewConstraint.layoutRelation = relation;viewConstraint.secondViewAttribute = attr;[children addObject:viewConstraint];}MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];compositeConstraint.delegate = self.delegate;[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];return compositeConstraint;} else {NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");self.layoutRelation = relation;self.secondViewAttribute = attribute;return self;}};
}

方法 equalToWithRelation 是没有参数的,但是 equalTo 方法返回的 block 中调用该方法时,似乎是传递了两个参数,这实际是同样的道理,参数实际是传递给 equalToWithRelation 函数返回的 block 的。

如果参数 attribute 是数组,即同时要添加多个约束,那么便创建一个 MASCompositeConstraint 实例来保存这些约束的相关属性。并且,这里将代理同样赋值给该实例。

从重写的 addConstraintWithLayoutAttribute: 可知,该代理实现了新增约束的操作,十分重要,其究竟是哪个类实现的呢!MASCompositeConstraint 作为 MASViewConstraint 的集合和代理,是否实现了新增约束的操作呢。

MASCompositeConstraint

MASCompositeConstraintMASConstraint 的子类,由一组 MASViewConstraint 初始化得到。其对父类抽象方法的实现,实际就是对其所持有的每个 MASViewConstraint 实例执行相应方法。即,该类可以对一组约束执行相同的操作。

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];return self;
}- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {id<MASConstraintDelegate> strongDelegate = self.delegate;MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];newConstraint.delegate = self;[self.childConstraints addObject:newConstraint];return newConstraint;
}

其实现的代理函数,依然只是将消息传递给了原本的代理对象。那么,这个真正的代理是谁,又执行了什么操作呢。

MASConstraintMaker

MASConstraintMaker 的属性来看,似乎同 MASConstraint 类很是相同,但它是 NSObject 的子类,和 MASConstraint 并没有继承关系。

类似的该类中有如下方法:

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

MASViewConstraint 中不同的是,这里的 addConstraintWithLayoutAttribute: 方法直接调用了自身的 constraint:addConstraintWithLayoutAttribute: 方法,而不是代理的相应方法。

这方法是 MASConstraintDelegate 代理声明的,且当前类实现如下:

- (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 constraintNSArray *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;
}- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {NSUInteger index = [self.constraints indexOfObject:constraint];NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}

可知,其根据自身关联的 view 和参数提供的位置属性创建一个 MASViewConstraint 实例对象,并且如果该方法的执行是由存在的 MASViewConstraint 实例引发的(即 constraint 参数存在),那么将两者合为一个 MASCompositeConstraint 实例对象,并替代引发该操作的实例。

最后,将得到的实例保存到 self.constraints 中。

该类同样定义了 install 方法,用来创建具体的约束。

- (NSArray *)install {if (self.removeExisting) {NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];for (MASConstraint *constraint in installedConstraints) {[constraint uninstall];}}NSArray *constraints = self.constraints.copy;for (MASConstraint *constraint in constraints) {constraint.updateExisting = self.updateExisting;[constraint install];}[self.constraints removeAllObjects];return constraints;
}

但是,这里会先根据 self.removeExisting 属性来判断是否要将关联的 view 中约束清空。

而且属性 self.updateExisting 同样是判断是否更新已存在约束的依据,这里更新的只是 NSLayoutConstraintconstant 属性,实际如果其他属性不同,则不会被认为是同一个约束。

并且,该方法会清空 self.constraints 中的成员,表示所有约束创建完毕。

那么,我们现在可以直接创建 MASConstraint 子类对象,并调用 install 方法来创建约束,也可以使用 MASConstraintMaker 来创建约束,那么那种方式更好呢。

实际上,如果直接使用 MASConstraint 来创建约束是无法使用链式编程的,因为其返回的 block 执行时,实际是向代理传递了消息,而此时代理并没有设置。

View+MASAdditions.h

Masonry 框架为 UIView 创建了分类,方便约束的创建。理解上面的约束创建过程,再来看这几个方法,则一目了然了。

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {self.translatesAutoresizingMaskIntoConstraints = NO;MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];block(constraintMaker);return [constraintMaker install];
}- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {self.translatesAutoresizingMaskIntoConstraints = NO;MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];constraintMaker.updateExisting = YES;block(constraintMaker);return [constraintMaker install];
}- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {self.translatesAutoresizingMaskIntoConstraints = NO;MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];constraintMaker.removeExisting = YES;block(constraintMaker);return [constraintMaker install];
}

添加、更新、重置约束,都是根据 MASConstraintMaker 中的属性来控制的。

[self.button mas_makeConstraints:^(MASConstraintMaker *make) {make.left.top.equalTo(@20);
}];

在 block 中,make.left 返回的是 MASConstraint 实例,之后调用 top 方法,实际是调用了 delegate 的 constraint:addConstraintWithLayoutAttribute: 方法,而该代理实际就是 make 对象,然后返回了一个 MASCompositeConstraint 实例对象,调用 equalTo 方法,进而调用了如下方法:

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {return ^id(id attr, NSLayoutRelation relation) {for (MASConstraint *constraint in self.childConstraints.copy) {constraint.equalToWithRelation(attr, relation);}return self;};
}

这样,便实现了对多个约束设置相同偏移量的操作。

这篇关于Masonry 框架小结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036176

相关文章

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

Java Stream 并行流简介、使用与注意事项小结

《JavaStream并行流简介、使用与注意事项小结》Java8并行流基于StreamAPI,利用多核CPU提升计算密集型任务效率,但需注意线程安全、顺序不确定及线程池管理,可通过自定义线程池与C... 目录1. 并行流简介​特点:​2. 并行流的简单使用​示例:并行流的基本使用​3. 配合自定义线程池​示

Java实现复杂查询优化的7个技巧小结

《Java实现复杂查询优化的7个技巧小结》在Java项目中,复杂查询是开发者面临的“硬骨头”,本文将通过7个实战技巧,结合代码示例和性能对比,手把手教你如何让复杂查询变得优雅,大家可以根据需求进行选择... 目录一、复杂查询的痛点:为何你的代码“又臭又长”1.1冗余变量与中间状态1.2重复查询与性能陷阱1.

Go之errors.New和fmt.Errorf 的区别小结

《Go之errors.New和fmt.Errorf的区别小结》本文主要介绍了Go之errors.New和fmt.Errorf的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考... 目录error的基本用法1. 获取错误信息2. 在条件判断中使用基本区别1.函数签名2.使用场景详细对

C#异步编程ConfigureAwait的使用小结

《C#异步编程ConfigureAwait的使用小结》本文介绍了异步编程在GUI和服务器端应用的优势,详细的介绍了async和await的关键作用,通过实例解析了在UI线程正确使用await.Conf... 异步编程是并发的一种形式,它有两大好处:对于面向终端用户的GUI程序,提高了响应能力对于服务器端应

MySQL慢查询工具的使用小结

《MySQL慢查询工具的使用小结》使用MySQL的慢查询工具可以帮助开发者识别和优化性能不佳的SQL查询,本文就来介绍一下MySQL的慢查询工具,具有一定的参考价值,感兴趣的可以了解一下... 目录一、启用慢查询日志1.1 编辑mysql配置文件1.2 重启MySQL服务二、配置动态参数(可选)三、分析慢查

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

解决若依微服务框架启动报错的问题

《解决若依微服务框架启动报错的问题》Invalidboundstatement错误通常由MyBatis映射文件未正确加载或Nacos配置未读取导致,需检查XML的namespace与方法ID是否匹配,... 目录ruoyi-system模块报错报错详情nacos文件目录总结ruoyi-systnGLNYpe

gorm乐观锁使用小结

《gorm乐观锁使用小结》本文主要介绍了gorm乐观锁使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录前言grom乐观锁机制gorm乐观锁依赖安装gorm乐观锁使用创建一个user表插入数据版本号更新总结前言乐观锁,顾名