Masonry
源码阅读
阅读源码是一种美妙的体验
这么强大的布局库,就不做解释了。因为系统的自动布局写起来很麻烦,所以 Masonry 成了当前流行的使用代码布局的方式(当然是在OC中)
具体使用如下:
[self.headView addSubview:self.headLabel];
[self.headLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-2);
make.left.mas_equalTo(13);
make.height.mas_equalTo(15);
}];
Masonry
也是支持链式调用的。
view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
不做解释了。
阅读源码是重点
Masonry
在 手机 开发 和 Mac 开发都能用,所以宏定义了
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
#elif TARGET_OS_MAC
#import
#define MAS_VIEW NSView
#define MASEdgeInsets NSEdgeInsets
typedef NSLayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;
#endif
根据平台的不同,重新定义了 MAS_VIEW 和 MASEdgeInsets
在布局之前,内部代码会自动加上self.translatesAutoresizingMaskIntoConstraints = NO;
,所以不用我们外部加。
跟一个布局流程,看看内部怎样做的。
内部定义了一个 MAS_VIEW
的 分类,这样子我们在外部,只要是UIView
或者 UIView
的子类,然后引入头文件,就可以调用方法了。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
/// 设置自动布局。
self.translatesAutoresizingMaskIntoConstraints = NO;
/// 生成 MASConstraintMaker
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
/// 执行外部的block
block(constraintMaker);
/// 安装布局信息
return [constraintMaker install];
}
mas_makeConstraints
添加布局信息
- 生成
MASConstraintMaker
block(constraintMaker)
把上一步生成的对象作为参数。然后调用外面的block
,重点是设置 上一步生成对象的属性- 让布局信息生效
接下来,我们的注意力只要转移到 MASConstraintMaker
就行了
生成 MASConstraintMaker
对象的过程比较简单
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
- 用 view 初始化一个对象 这里 view 的引用是
weak
的。 - 创建一个
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;
}
- 判断要不要删除之前已经存在的约束
removeExisting
,如果要删除,则调用MASConstraint 的 uninstall
方法卸载掉约束removeExisting
只有在mas_remakeConstraints
的时候才为YES
- 遍历
self.constraints
然后调用MASConstraint 的 install
方法,让约束生效 - 清空
self.constraints
数组,因为保存的约束都已经生效了。
接下来只要分析 MASConstraint
思路就清楚了
MASConstraint
是MAS库,封装的,关于约束的一个类,外面能够进行链式调用的设置约束,也是得益于MASConstraint
。定义了很多常用的约束操作。比如:
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
~~~~~ 还有N多个操作。
上面的函数都返回自身对象,所以才能够进行链式调用。
我们上面的例子中
block 执行了
make.bottom.mas_equalTo(-2);
make.left.mas_equalTo(13);
make.height.mas_equalTo(15);
因为 bottom left height
属性都是lazy load 的,所以只要调用了,就会添加一个约束给 view
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
最终会调用 ps(我们标记一下这个函数 为 S)
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
/// 生成 MASViewAttribute MASViewAttribute 是用来描述一个view 和 约束的 关系
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
/// 生成 MASViewConstraint MASViewConstraint 是 MAS描述约束的。
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
/// 我们先记下S这里的作用,以后看看作用,
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;
}
到这一步,可以看到
self.constraints
中存的是MASViewConstraint
对象,并且把代理指向了maker
设置约束的值,一般使用已经定义好的宏
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
如果不想使用
mas
开头的宏,可以在全局定义MAS_SHORTHAND_GLOBALS
比如:
#define MAS_SHORTHAND_GLOBALS
就可以了
所以,类似
栗子 A
UIButton *button = [UIButton new];
[self.view addSubview:button];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10);
make.top.mas_equalTo(10);
make.size.mas_equalTo(CGSizeMake(20, 20));
}];
中设置 left.mas_offset(10);
左边距为10 最后就是调用,MASConstraint
的
- (MASConstraint * (^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
函数,函数同样是返回一个 block
,并且 block
中返回self
方便链式调用.
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
setLayoutConstantWithValue
函数中,会判断设置的值得类型,然后进行不同的设置,根据约束的不同 比如:NSLayoutAttributeWidth NSLayoutAttributeLeft
等等。。不做多余解释,
跟 栗子 A 有同样作用的链式调用,可以这样子写。
栗子 B
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10).top.mas_equalTo(10);
make.size.mas_equalTo(CGSizeMake(20, 20));
}];
不同之处在于
make.left.mas_offset(10).top.mas_equalTo(10);
第一个 .left
是 MASConstraintMaker
中的属性,调用完成后会返回 自己。 第二个 top
是 MASViewConstraint
自己的。下面分析一下,这个怎么玩的。
你看,调用 .top 的时候,MASViewConstraint
没有做什么事情,直接调用 delegate
的 [constraint:addConstraintWithLayoutAttribute:];
方法
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
峰回路转,调用到我们刚才标记的 S 那里了,这回,第一个参数有值了。我们可以揭开神秘的面纱了。
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;
}
- 首先生成
MASCompositeConstraint
对象。 - 然后把 对象的代理设置为
self
- 然后替换 传进来的第一个参数在
constraints
中的值。 - 最后返回 对象,因为
MASCompositeConstraint
继承自MASConstraint
所以后面的链式调用也是可以继续的。
替换函数如下
- (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];
}
之所以要替换,是因为 防止在最后 遍历
self.constraints
设置约束的时候重复 因为MASCompositeConstraint
也是继承自MASConstraint
和MASViewConstraint
一样 实现了很多之后要用到的方法。比如install
等等。
MASConstraint
定义了约束 操作的函数,但是对约束的操作都放在子类中进行
MASViewConstraint
一个单独的约束MASComposisteConstraint
一组约束
MASConstraint
中需要子类重写的方法,都抛出了异常
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
MASViewConstraint
单独的约束,重写了 MASConstraint
标记的需要重写的方法。在父类链式调用的基础上,增加了。
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
用来描述跟约束相关的信息。
MASCompositeConstraint
代表一组约束。内部存放了一个私有变量
@property (nonatomic, strong) NSMutableArray *childConstraints;
用来存放单个的约束的信息,一般都是存放 MASViewConstraint
.
mas_makeConstraints
mas_updateConstraints
mas_remakeConstraints
函数的区别:
updateConstraints
函数里里会设置updateExisting
为 YES
remakeConstraints
函数里会设置removeExisting = YES;
如果 updateExisting
为 YES
的时候再一个约束install
的时候会先寻找当前view
有没有相同的约束,如果有,就直接更新约束的 constant
值。代码如下:
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
}
如果 removeExisting = YES;
在 install
之前,会删除view
的所有约束
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
Masonry 这个经常使用库我们就摸到了冰山一角了。以后有空一行一行的扣一下细节