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

相关文章

判断PyTorch是GPU版还是CPU版的方法小结

《判断PyTorch是GPU版还是CPU版的方法小结》PyTorch作为当前最流行的深度学习框架之一,支持在CPU和GPU(NVIDIACUDA)上运行,所以对于深度学习开发者来说,正确识别PyTor... 目录前言为什么需要区分GPU和CPU版本?性能差异硬件要求如何检查PyTorch版本?方法1:使用命

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

Spring Boot读取配置文件的五种方式小结

《SpringBoot读取配置文件的五种方式小结》SpringBoot提供了灵活多样的方式来读取配置文件,这篇文章为大家介绍了5种常见的读取方式,文中的示例代码简洁易懂,大家可以根据自己的需要进... 目录1. 配置文件位置与加载顺序2. 读取配置文件的方式汇总方式一:使用 @Value 注解读取配置方式二

Python中的getopt模块用法小结

《Python中的getopt模块用法小结》getopt.getopt()函数是Python中用于解析命令行参数的标准库函数,该函数可以从命令行中提取选项和参数,并对它们进行处理,本文详细介绍了Pyt... 目录getopt模块介绍getopt.getopt函数的介绍getopt模块的常用用法getopt模

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表

Java中Scanner的用法示例小结

《Java中Scanner的用法示例小结》有时候我们在编写代码的时候可能会使用输入和输出,那Java也有自己的输入和输出,今天我们来探究一下,对JavaScanner用法相关知识感兴趣的朋友一起看看吧... 目录前言一 输出二 输入Scanner的使用多组输入三 综合练习:猜数字游戏猜数字前言有时候我们在

SQL BETWEEN 的常见用法小结

《SQLBETWEEN的常见用法小结》BETWEEN操作符是SQL中非常有用的工具,它允许你快速选取某个范围内的值,本文给大家介绍SQLBETWEEN的常见用法,感兴趣的朋友一起看看吧... 在SQL中,BETWEEN是一个操作符,用于选取介于两个值之间的数据。它包含这两个边界值。BETWEEN操作符常用

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

python uv包管理小结

《pythonuv包管理小结》uv是一个高性能的Python包管理工具,它不仅能够高效地处理包管理和依赖解析,还提供了对Python版本管理的支持,本文主要介绍了pythonuv包管理小结,具有一... 目录安装 uv使用 uv 管理 python 版本安装指定版本的 Python查看已安装的 Python