自动布局 Auto Layout (原理篇)
背景
Auto Layout是苹果公司在iOS6发布的界面布局技术,并随着iOS SDK的迭代逐步完善了各种布局API、提供多种使用Auto Layout的布局方式。实际上Auto Layout算法本身并非有Apple发明,Auto Layout源于Cassary约束解析工具包。该算法由Alan Borning、Kim Marriott、Peter Stuckey、Yi Xiao于1997年发布,而后被多门流行编程语言采用,Objective-C是其中之一。
Auto Layout本质
Cassary该算法的主要思想是:将基于约束系统的布局规则(本质上是表示视图布局关系的线性方程组)转化为表示规则的视图几何参数。
-
基于Auto Layout的布局,不在需要像frame时代一样,关注视图尺寸、位置的常数,转而关注视图之间关系,描述一个表示视图间布局关系的约束集合,由Auto Layout Engine解析出最终数值。
-
一个约束对象NSLayoutConstraint,本质上是表示两个视图之间布局关系的一个线性方程,该方程可以是线性等式、也可以是线性不等式。
-
多个约束对象组成是一个约束集合,本质上是表示某个界面上多个视图之间布局关系的线性方程组。约束不充分,可能导致视图丢失,视图错位。
图片.pngAuto Layout布局周期
Auto Layout布局过程涉及延迟机制,并非一有约束更新就马上进行布局重绘,当有约束更改时,系统的默认做法是延迟更新,目的是实现批量更改约束、绘制视图,避免频繁遍历视图层级,优化性能。当更新约束太慢影响到后序代码逻辑,也可强制马上更新。
图片.png
关于Auto Layout的布局流程,Apple给出图示如上:即Layout Cycle是一个在App运行循环RunLoop下循环执行的一个过程。
- App启动后开启RunLoop,循环检测图层树中是否存在约束变化;
- 当发生Constrints Change(直接or间接设置、更新、移除约束),RunLoop检测到约束变化;
- RunLoop发现约束变化后,就会进入Deferred Layout阶段,视图的位置、尺寸值会在这个过程计算,设置到对应视图上,并绘制出来;
- 执行完一轮布局,RunLoop会继续检查视图树的约束更新情况,当再次发现约束更新,则执行新一轮布局……
Auto Layout多种使用方式
一、NSLayoutConstraint对象创建约束
原生的NSLayoutConstraint进行布局,使用NSLayoutConstraint提供的约束对象创建接口,传入对应的参数即可,一个约束对象对应一个布局关系。具体步骤如下:
设置View的translatesAutoresizingMaskIntoConstraints属性为NO;
根据约束方程式,创建约束对象;
把约束添加到对应位置(iOS8+直接通过active激活约束);
NSLayoutConstraint优缺点:
1、使用该方式进行布局,最明显的特点是代码量冗余,欠优雅。
2、约束更新、删除时,执行起来也不方便,需要实现用指针记录约束对象、或者通过匹配找到对应的约束。
UIView *grayView = [[UIView alloc] init];
grayView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:grayView];
grayView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:50];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:grayView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];
[self.view addConstraints:@[left, top]];
[grayView addConstraints:@[width, height]];
二、VFL创建约束
- VFL,即Visual Format Language,可视化格式语言。这种布局方式,同样是使用NSLayoutConstraint类来创建约束。不同之处在于:上面演示的NSLayoutConstraint方式是基于线程方程式创建约束,一个约束对象表示一个约束关系。而VFL是使用字符串编码的方式创建约束,Auto Layout根据字符串创建对应的约束对象。
VFL字符串中可以传入任意多个视图、可以表示任意多个布局关系,因此使用VFL创建的约束时一次创建一个约束集合,返回一个装着NSLayoutConstraint对象的数组。
NSArray<NSLayoutConstraint *> *horizontalConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view1(100)]-50-[view2]-50-|" options:(NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom) metrics:nil views:NSDictionaryOfVariableBindings(view1, view2)];
[self.view addConstraints:horizontalConstraint];
NSArray<NSLayoutConstraint *> *verticalConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view1(100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view1, view2)];
[self.view addConstraints:verticalConstraint];
VFL优缺点
- VFL使用简短的字符串指定布局关系,对一个布局字符串中传入的视图个数、布局关系个数不做限制,约束代码简洁。
- 某些约束关系无法使用VFL约束规则来描述,例如尺寸比例(A的宽度是B的宽度的2倍)。
- 字符串编码的固有缺陷:Xcode无法在编译期间检查约束,只能在运行时生效,安全性低。Xcode中把字符串颜色设置为警告效果的红色,应该是表示这部分代码编译器无能为力,程序猿自求多福。
- 企业开发一般不会使用VFL,此处仅做简单介绍,详见 Apple官方教程VFL。Auto Layout 约束冲突时Log信息一般是以VFL语言展示,最好要能读懂。
三、Interface Builder
- Apple建议开发中使用Interface Builder进行布局,使用该方式进行布局,Xcode自带约束冲突、约束歧义检查。这种布局方式开发速度相对较快,遇到比较复杂的布局也可以结合代码进行。详见 Apple官方教程Interface Builder
四、Masonry
- Masonry ,对应的Swift版本是SnapKit,企业开发中绝大多数都会使用该库作为布局方案。该库接口设计优雅,只需要少量代码即可实现布局、布局更新。
- translatesAutoresizingMaskIntoConstraints接口文档介绍:决定是否把UIView的autoresizing mask转化为Auto Layout约束集合的Bool值。autoresizing mask是Auto Layout之前的一种布局方案,Apple为了向后兼容,允许前者转换成后者。translatesAutoresizingMaskIntoConstraints默认设为YES,即autoresizing mask将会转换auto layout对应的约束集合(对应的约束类是NSAutoresizingMaskLayoutConstraint),且这些约束已经完全确定了UIView的位置和尺寸,如果额外添加约束就会造成约束冲突。* 该属性默认为YES,使用Auto Layout布局时需要设置为NO。使用Masonry则不用手动设置,底层帮我们设置好了。