Masonry
是iOS在控件布局中经常使用的一个轻量级框架,Masonry
让NSLayoutConstraint使用起来更为简洁,也是函数式编程的典范。废话少说,下面我们来分析Masonry具体源码。
在分析Masonry
源码前,我们来回顾下系统的NSLayoutConstraint
布局。用过的同学都会认为很蛋疼,代码量很大,重复性的代码很浪费时间,废话少说,直接上代码。
NSLayoutConstraint *constraint1 = [NSLayoutConstraint
constraintWithItem:subView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:40];
上面约束代码用表达式描述: subview.top = superView.top *1 +40
;
用文字描述:subView
的某个属性(attr1
)等于superView
的某个属性(attr2
)的值的多少倍(multiplier
)加上某个常量(constant
)
用Masonry
写这个约束:
make.top.equalTo(@40);
or
make.top.equalT0(superView.mas_top).offset(40);
都是表达的subview.top = superView.top *1 +40
;
显而易见Masonry比NSLayoutConstraint写法方便许多。而且还是链式调用,更加的方便,废话少说,分析
UILabel *testLabel = [[UILabel alloc] init];
testLabel1.text = @"songhongkang";
[self.view addSubview:testLabel];
[testLabel1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@100);
make.left.equalTo(@50);
make.right.equalTo(@-50);
}];
View+MASAdditions 分类中有些语法先解释下
#if TARGET_OS_IPHONE || TARGET_OS_TV
#endif
如果if中的条件是YES,if里面的代码将会被编译,如果if中的条件是NO,if里面的代码将不会被编译
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
// 如果要使用autolayout 'translatesAutoresizingMaskIntoConstraints' 属性 必须是No
// 默认是YES
self.translatesAutoresizingMaskIntoConstraints = NO;
// 创建 MASConstraintMaker 对象, 把当前视图传给MASConstraintMaker 保存
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//把constraintMaker传给block
block(constraintMaker);
// 安装约束
return [constraintMaker install];
}
make.top 调用 MASConstraintMaker 的 top get方法:
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// 分析
//make.top.equalTo(@100);
//make.left.equalTo(@50);
//make.right.equalTo(@-50);
//constraint参数为nil
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
// 实例化一个 MASViewAttribute 对象.
// self.view 需要添加约束的视图(view) 传过去
// layoutAttribute 约束的属性 (NSLayoutAttributeLeft、NSLayoutAttributeTop、NSLayoutAttributeRight) 等
// viewAttributed对象中的属性已经被赋值了
// view 、 item 、layoutAttribute 都已经被赋值
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
// 实例化一个 MASViewConstraint 对象, 并且给这个对象的属性firstViewAttribute 赋值, 把viewAttribute赋值给firstViewAttribute属性
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
// constraint 为 nil ,不执行这个if
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;
}
// constraint 为 nil ,执行这个if
if (!constraint) {
// 给MASViewConstraint对象设置代理,为了是链式调用 eg:
// make.left.equal(@10);
newConstraint.delegate = self;
// 把所有的约束对象放到当前对象的constraints数组中
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
make.top.equalTo(@100); 调用 MASConstraint 的 equalTo get方法:
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
都要在子类中实现
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
// eg: make.left.equalTo(@10);
// attribute = 10; relation = NSLayoutRelationEqual
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 {
// #define NSAssert(condition, desc)
//condition是条件表达式,值为YES或NO;desc为异常描述,通常为NSString。当conditon为YES时程序继续运行,为NO时,则抛出带有desc描述的异常信息。NSAssert()可以出现在程序的任何一个位置。
// self.hasLayoutRelation 表示:
// self.layoutRelation == relation 表示:
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
// eg: make.left.equalTo(@10);
// attribute = 10; relation = NSLayoutRelationEqual
// 再去调用set方法, 把属性 self.hasLayoutRelation 设置YES
self.layoutRelation = relation;
// set方法 根据attribute类型有不同的赋值,具体可以看看
// 通过一系列赋值 对象中的属性 layoutConstant 已经有值了。
// eg: make.left.equalTo(@10);
//layoutConstant = 10
// eg: make.left.equalTo(self.view).offset(10);
//layoutConstant = self.view
self.secondViewAttribute = attribute;
return self;
}
};
}
secondViewAttribute set方法可以关注下,感觉写法比较6
- (void)setSecondViewAttribute:(id)secondViewAttribute {
// make.left.equalTo(@10)
// 数字
// uiview
// MASViewAttribute
//
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
// make.left.equalTo(self.view).offset(100);
// secondViewAttribute 是 self.view
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
_secondViewAttribute = secondViewAttribute;
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
到目前所有的约束都放到MASConstraintMaker对象的constraints属性中了
return [constraintMaker install];
- (NSArray *)install {
//- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
//- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
// 现在分析 的是mas_makeConstraints方法... 所以self.removeExisting是NO
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
// eg:
// make.top.equalTo(@100);
// make.left.equalTo(@50);
// make.right.equalTo(@-50);
// constraints 数组现在存放都是 MASViewConstraint的对象
NSArray *constraints = self.constraints.copy;
// 把约束对象遍历出来,并且把updateExisting 更新约束赋值
//- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
// 现在调用的是此方法,所以self.updateExisting 为NO
// 安装约束,具体实现看 install 方法的注释
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}
主要到的一点,看看约束怎么安装在公共的视图上面
安装约束在MASViewConstrain类中
- (void)install {
// 约束已经安装,返回return, 此方法的下面的代码不会被执行
//
if (self.hasBeenInstalled) {
return;
}
// 此处分析是约束条是
//make.top.equalTo(@100);
//make.left.equalTo(@50);
//make.right.equalTo(@-50);
// layoutConstraint 始终没有被初始化过, 这个if条件不会被执行
// 代码具体是什么意思。后面分析
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
// firstLayoutItem 就是Label
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
//firstLayoutAttribute, NSLayoutAttributeLeft
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10) self.secondViewAttribute 为 nil
// eg.make.left.equatTo(self.view.mas_top) self.secondViewAttribute 为self.view
// self.firstViewAttribute.isSizeAttribute 为YES , 表示这个约束是
//NSLayoutAttributeWidth 。 NSLayoutAttributeHeight
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
// self.firstViewAttribute.view 父视图赋值给 secondLayoutItem
secondLayoutItem = self.firstViewAttribute.view.superview;
// firstLayoutAttribute 赋值给 secondLayoutAttribute
secondLayoutAttribute = firstLayoutAttribute;
}
// 生成一个约束对象 NSLayoutConstraint
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
// 默认的约束优先级是 1000
layoutConstraint.priority = self.layoutPriority;
// mae_key 这个参数为了方便调试约束
layoutConstraint.mas_key = self.mas_key;
// 如果seconndViewAttribute.view 有值
// eg:make.left.equalTo(self.view);
if (self.secondViewAttribute.view) {
// self.secondViewAttribute.view = self.view
// self.firstViewAttribute.view = label;
// 找到两个视图的最近的公共视图 ,可以看我的注释,这个方法在View+MASAdditions类中
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
// 如果约束是 NSLayoutAttributeWidth、 NSLayoutAttributeHeight
self.installedView = self.firstViewAttribute.view;
} else {
// eg:make.left.eqaulTo(@100);
self.installedView = self.firstViewAttribute.view.superview;
}
MASLayoutConstraint *existingConstraint = nil;
//- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
// 现在调用的是此方法,所以self.updateExisting 为NO
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
//- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
// 现在调用的是此方法,所以existingConstraint 为NO
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
// - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
// 调用此方法,就会执行下面代码
[self.installedView addConstraint:layoutConstraint];
// 把变量赋值给全局变量 , 不知道有什么用
self.layoutConstraint = layoutConstraint;
// 把MASViewConstraint 约束保存起来
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
经常我们需要给某一个UIView添加约束,那么它的约束应该添加到那个视图上了?
其实单纯的说添加给他们两个哪一个控件都是不够准确的,当然在一些情况下也有可能是两个Item中的一个。其实应该是加在离他们最近的公共视图上。 怎么找最近的公共视图,(这里就的思想有点像求最小公倍数)代码如下:
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil; // 保存公共的父视图
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) { //遍历secondViewSuperview的父视图
if (secondViewSuperview == firstViewSuperview) { // 如果有相同的
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
// 返回最近的父视图
return closestCommonSuperview;
}
记录下,有空再来更新下。 2018年01月18日17:53:43
网友评论