Autolayout
介绍
自动布局区别于手动布局frame的一种布局技术,主要是为元素添加一些关于位置的相对约束关系,而非直接设置frame的位置,相对位置最终会被布局引擎换算成绝对位置,这样更能够适配不同屏幕和系统
使用
- storyboard
- 代码实现 AutoLayout 步骤
1.利用 NSLayoutConstraint 类创建具体的约束对象;
2.添加约束对象到相应的 view 上
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"Autolayout布局";
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:blueView];
// 添加width约束
NSLayoutConstraint *blueWidthConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
[blueView addConstraint:blueWidthConstraint];
// 添加height约束
NSLayoutConstraint *blueHeightConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150];
[blueView addConstraint:blueHeightConstraint];
// 添加left约束
NSLayoutConstraint *blueLeftConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
[self.view addConstraint:blueLeftConstraint];
// 添加top约束
NSLayoutConstraint *blueTopConstraint = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
[self.view addConstraint:blueTopConstraint];
/*UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
NSLayoutConstraint *redWidthConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
[redView addConstraint:redWidthConstraint];
NSLayoutConstraint *redHeightConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:150];
[redView addConstraint:redHeightConstraint];
NSLayoutConstraint *redLeftConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
[self.view addConstraint:redLeftConstraint];
NSLayoutConstraint *redTopConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:100];
[self.view addConstraint:redTopConstraint];
}*/
NSLayoutConstraint *redLeftConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
[NSLayoutConstraint constraintWithItem:view1 attribute:attr1 relatedBy:relation toItem:view2 attribute:attr2 multiplier:1.0 constant:c];
view1: 要约束的控件(redView)
☞ attr1: 约束的属性(常量),(这里是NSLayoutAttributeLeft)
☞ relation: 与参照控件之间的关系(常量),包括等于、大于等于、小于等于
☞ view2: 参照的控件(self.view)
☞ attr2: 约束的属性(常量)
☞ multiplier: 倍数(1.0)
☞ c: 常量,做好了上述的约束之后会加上这个常量(50)
所以我们得出 AutoLayout 的核心计算公式:
obj1.property1 =(obj2.property2 * multiplier)+ constant value
原理
约束-》布局-》渲染
公式: view1.attr1 = view2.attr2 * multiplier + constant
当为View增加Constraint时,这些约束会组成一个多元一次方程组,View所在的Window会调用Auto Layout 引擎计算得到解集equation,其中,求出的解集会应用到 UIView渲染过程中,当做其 frame属性中的值来使用
Masonry
用于自动布局的第三方库,使用方便,代码简洁、优雅
用法
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(50);
make.top.equalTo(blueView.mas_bottom).offset(100);
make.width.mas_equalTo(100);
make.height.mas_equalTo(150);
}];
1.mas_makeConstraints
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//默认情况下它是YES,即view的autoresizing mask会自动成为它的布局。如果我们希望手动布局,需要将它设为NO
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
MASConstraintMaker负责为视图创建各个属性的约束,[constraintMaker install] 为视图添加约束
2.make(MASConstraintMaker)
make.left.equalTo(self.view).offset(50);
// step 1
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// step 3
- (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 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;
}
MASViewAttribute是约束属性类,存储着view、视图属性等关系
MASViewConstraint 这是一个约束,它包含firstViewAttribute和secondViewAttribute。firstViewAttribute为当前视图的约束属性,secondViewAttribute是要参照视图的属性
// step 4
- (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;
}
};
}
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
_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);
}
}
equalTo将传入的参数赋值给MASViewConstraint的secondViewAttribute
make.left.equalTo(self.view).offset(50)这句代码为当前视图的某一个属性创建一个与参照视图的约束关系
3.链式调用
make.left.top.equalTo(self);
make.left的返回类型是MASViewConstraint,那MASViewConstraint中是如何调用到top的呢?
从MASViewConstraint的父类MASConstraint可以看到,这里也定义了所有的布局属性,但实际是将添加约束这个操作委托给了代理方法,如下,例:
// MASConstraint.m
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
// MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
创建MASViewConstraint的时候,将delegate设置为MASConstraintMaker,因此实际是调用了之前step3的方法,但与之前不同的是,第一个参数不是nil,而是self,即make.left返回的约束对象
// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// step 3
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
为视图创建了多个属性的组合约束MASCompositeConstraint,equalTo则为组合约束中的每一个单独的约束对象添加参照视图约束关系
4.[constraintMaker install]
[constraintMaker install]其实没做什么,,主要的工作是[constraint install]
创建一个约束对象,然后添加到最近的公共superview上
5.updateConstraint和remakeConstraint
MASConstraintMaker中有两个属性updateExisting和removeExisting,install的时候根据这两个属性,如果是更新约束,直接更新constant值更新约束;如果是remake则将当前视图install过的约束uninstall之后再重新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;
}
性能
在使用 Auto Layout 进行布局时,可以指定一系列的约束,比如视图的高度、宽度等等。而每一个约束其实都是一个简单的线性等式或不等式,整个界面上的所有约束在一起就明确地(没有冲突)定义了整个系统的布局。
因为布局系统在最后仍然需要通过 frame 来进行,所以 Auto Layout 虽然为开发者在描述布局时带来了一些好处,不过它相比原有的布局系统加入了从约束计算 frame 的过程,
参考文章
AutoLayout&Masonry&约束的底层实现原理
寒哥细谈之 AutoLayout 全解
史上比较用心的纯代码实现 AutoLayout
从 Auto Layout 的布局算法谈性能
Masonry实现原理并没有那么可怕
WWDC2018观看笔记一:Auto Layout 底层原理