Masonry 源码进阶
Masonry源码阅读配合下面两篇文章足矣。第一篇比较简单,主讲大框架。第二篇比较详细,细节点较多。那我呢?我来讲讲进阶吧。讲一些
Draveness blog
from cocoachina
先看看原生的布局是怎么做的。
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIView *view2 = [[UIView alloc] init];
view2.translatesAutoresizingMaskIntoConstraints = NO;
view2.backgroundColor = [UIColor blueColor];
[superview addSubview:view2];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeLeft
multiplier:1
constant:-padding.right],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0],
//view2 constraints
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:view1
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view2
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
Masonry做的事情就用点语法方便的把整个过程封装了起来。比如
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top]
等价于 Masonry 的。当然此时是view1调用了makeConstraints函数
make.top.mas_equalTo(superview.top).offset(padding.top)multipliedBy(1);
读 masonry源码还需要有点语法+block 的基础,读者自行补充。导读开始!show time~
Tip1:Autoresizing
self.translatesAutoresizingMaskIntoConstraints = NO;
self.translatesAutoresizingMaskIntoConstraints = NO;
关闭Autoresizing。 不懂的可以看看这个。如果是 YES,autolayout将无效。
Autoresizing相关 blog
Tip2:make.left.right.top.bottom发生了什么
make.left.right.top.bottom.mas_equalTo(superview)
到底发生了什么?一步一步推导!
make 是 MASConstraintMaker
make.left 是MASConstraintMaker的实例对象调用了 left 方法,make.left返回了newConstraint。记住newConstraint的类型是MASViewAttribute,很重要!
- (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]) {
...此处不是MASViewConstraint,所以忽略
}
if (!constraint) {
newConstraint.delegate = self;//设置了代理!
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
make.left.right ,make.left返回的是MASViewAttribute,所以这时候去MASViewAttribute的对象方法里面找 right。它的父类MASConstraint实现了 right 方法
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
然后
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
此时有一个代理,注意之前的代码! newConstraint.delegate = self
,代理是 make!所以有跑到了这里!
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
//这个时候constraint就是 make.left 产生的MASViewConstraint!!!
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) {
//此处不走!
}
return newConstraint;
}
so make.left.right返回了MASCompositeConstraint。里面有两个MASViewConstraint。MASCompositeConstraint里有childConstraints,里面存放着一个又一个的MASViewConstraint。
make.left.right.top ---
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
|
V
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:
id<MASConstraintDelegate> strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
这里调用strongDelegate去做 add 约束的动作。compositeConstraint.delegate = self;
strongDelegate就是 make,(make 内心是崩溃的,怎么又是我!)
- (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]) {
//此时是MASCompositeConstraint,所以也不发生!
}
if (!constraint) {
//..不是 nil,不发生。
}
return newConstraint;
}
所以make.left.right.top其实就[self.childConstraints addObject:newConstraint];
添加了一个新的约束。此时要注意一个细节,return newConstraint;
这个细节坑了我 N 久。这里返回了newConstraint,所以make.left.right.top返回的是MASViewConstraint?不是。这里返回了MASViewConstraint,但是没有去接收这个约束。MASCompositeConstraint返回的是 self。太狡诈了~~~
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
so make.left.right.top返回MASCompositeConstraint,且添加了一个约束。
make.left.right.top.bottom 这里和上面一样。
这里再次总结下!
make.left->MASViewConstraint
make.left.right-> MASCompositeConstraint
make.left.right.top-> MASCompositeConstraint
make.left.right.top.bottom-> MASCompositeConstraint
addConstraint这个动作都会在 make 中发生。
最后 make.left.right.top.bottom.mas_equalTo(superview)
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
遍历约束,相对于调用MASViewConstraint的equalTo方法。
Tip3:masory 巧妙架构
这个是对tip2的补充。
MASConstraint
是MASViewConstraint
和MASCompositeConstraint
的父类。
MASCompositeConstraint
的重点是有一个存放MASViewConstraint
的childConstraints
。由于继承了MASConstraint
所以又可以调用MASConstraint
的所有方法。使链式语法可以继续~
这里有一个思想!
父类,子类,子类组。
子类组用起来和子类没区别,但实际发生链式语法之后,每次都把新生成的子类收集到了自己里面,让自己变大。
make充当了一个启动器,产生了第一个MASViewConstraint,使后面链式可以跑起来! make 也充当了一个生成MASConstraint
生成器的角色,所有的MASConstraint
都来自make。这简直太妙了!我水平有限,不知道怎么恰当形容。
Tip4:mas_closestCommonSuperview
寻找共同的父控件到底发生了什么?下面的代码让我一度很困惑。我不能理解!closestCommonSuperview && firstViewSuperview
怎么可能会为0,后来我意识到firstViewSuperview.superview的父控件是有限的。它最后可能会为 nil。
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
他不能直接写成这样?
if self.superview == view.superview
return self.superview
else
return nil
然后我测试了下,两个视图的父控件不是一个,比如View1的爷控件等于 View2 的父控件,布局也是可以进行的。好吧,我 too naive。确实应该写成寻找共同最小父控件。
Tip5:NSLayoutAttributeLeftMargin是什么
iOS 8新增属性。下面两句话等价!
make.leftMargin.equalTo(10);
make.left.equalTo(another.left).offset(10);
这么用在父控件上当然可以!但是!
make.leftMargin.equalTo(10);
make.left.equalTo(superview.left).offset(10);
注意
如果superview是控制器的 self.view。那布局会出问题。会有一定的误差。这是系统问题。可以看看官方文档。
Tip6:优先级
MASLayoutPriorityRequired = UILayoutPriorityRequired;
MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
MASLayoutPriorityDefaultMedium = 500;
MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
每一条约束默认都是必须的,必须的意思是1000。我常用的就是这个。
//效果一样。
make.width.priority(749);
make.width.equalTo(@(10)).priorityLow();
//如果有两条约束,控件的高为60.
//假如你在外部调用[globalconstraint deactivate],此时高度就变成了30.
//其实这么用起来和 update 差不多。
make.height.equalTo(@30).priorityLow();
globalconstraint = make.height.equalTo(@60);
Tip7:group
我在网上找了很多 group 的用法,愣是没找着。我简单测试了下。其实 group 的用处就是可以返回MASCompositeConstraint。有什么用就靠你的想象力了!
make.width.height.equalTo(redView);
make.bottom.equalTo(blueView.top).offset(-padding);
make.group(^(){
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.right.equalTo(redView.left).offset(-padding);
});
make.height.equalTo(blueView.height);
make.width.height.equalTo(redView);
make.bottom.equalTo(blueView.top).offset(-padding);
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.right.equalTo(redView.left).offset(-padding);
make.height.equalTo(blueView.height);
最后附上本人 github 源码备注。欢迎交流技术!
Masonry源码备注