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

相关文章

Java中的ConcurrentBitSet使用小结

《Java中的ConcurrentBitSet使用小结》本文主要介绍了Java中的ConcurrentBitSet使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、核心澄清:Java标准库无内置ConcurrentBitSet二、推荐方案:Eclipse

Go语言结构体标签(Tag)的使用小结

《Go语言结构体标签(Tag)的使用小结》结构体标签Tag是Go语言中附加在结构体字段后的元数据字符串,用于提供额外的属性信息,这些信息可以通过反射在运行时读取和解析,下面就来详细的介绍一下Tag的使... 目录什么是结构体标签?基本语法常见的标签用途1.jsON 序列化/反序列化(最常用)2.数据库操作(

Java中ScopeValue的使用小结

《Java中ScopeValue的使用小结》Java21引入的ScopedValue是一种作用域内共享不可变数据的预览API,本文就来详细介绍一下Java中ScopeValue的使用小结,感兴趣的可以... 目录一、Java ScopedValue(作用域值)详解1. 定义与背景2. 核心特性3. 使用方法

spring中Interceptor的使用小结

《spring中Interceptor的使用小结》SpringInterceptor是SpringMVC提供的一种机制,用于在请求处理的不同阶段插入自定义逻辑,通过实现HandlerIntercept... 目录一、Interceptor 的核心概念二、Interceptor 的创建与配置三、拦截器的执行顺

C#中checked关键字的使用小结

《C#中checked关键字的使用小结》本文主要介绍了C#中checked关键字的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录✅ 为什么需要checked? 问题:整数溢出是“静默China编程”的(默认)checked的三种用

C#中预处理器指令的使用小结

《C#中预处理器指令的使用小结》本文主要介绍了C#中预处理器指令的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 第 1 名:#if/#else/#elif/#endif✅用途:条件编译(绝对最常用!) 典型场景: 示例

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

C# Semaphore与SemaphoreSlim区别小结

《C#Semaphore与SemaphoreSlim区别小结》本文主要介绍了C#Semaphore与SemaphoreSlim区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录一、核心区别概览二、详细对比说明1.跨进程支持2.异步支持(关键区别!)3.性能差异4.API 差

C#中Trace.Assert的使用小结

《C#中Trace.Assert的使用小结》Trace.Assert是.NET中的运行时断言检查工具,用于验证代码中的关键条件,下面就来详细的介绍一下Trace.Assert的使用,具有一定的参考价值... 目录1、 什么是 Trace.Assert?1.1 最简单的比喻1.2 基本语法2、⚡ 工作原理3

C# IPAddress 和 IPEndPoint 类的使用小结

《C#IPAddress和IPEndPoint类的使用小结》本文主要介绍了C#IPAddress和IPEndPoint类的使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定... 目录一、核心作用网络编程基础类二、IPAddress 类详解三种初始化方式1. byte 数组初始化2. l