一、What's Auto Layout
Auto Layout
是由苹果公司UIKit
框架提供的一个用于动态计算UIView
及其子类的大小和位置的库。html
说到Auto Layout
就不得不说Cassowary
算法,由于Auto Layout
是构建在Cassowary
算法的基础之上的。1997年,Auto Layout
用到的布局算法论文发表,被称为高效的线性方程求解算法。2011年苹果利用Cassowary
算法为开发者提供了Auto Layout
自动布局库中。因为Cassowary
算法的自己的优秀,不只是苹果公司,许多开发者将其运用到各个不一样的开发语言中,如JavaScript、ASP.NET、Java、C++
等都有运用Cassowary
算法的库。从这里也能够看出Cassowary
算法自身的优秀和先进性,否则不会被运用的如此普遍。前端
苹果公司在iOS 6
系统时引入了Auto Layout
,可是直到如今已经更新到iOS 12
了,还有不少开发者仍是不肯使用Auto Layout
。主要是对其反人类的语法以及对其性能问题的担心。git
针对Auto Layout
的一些问题,在iOS 9
发布时,苹果推出了更简洁语法的NSLayoutAnchor。同时发布了模仿前端Flexbox布局思路的UIStackView,以此为开发者在自动布局上提供更好的选择。github
在苹果WWDC 2018 High Performance Auto Layout中苹果工程师说: iOS 12将大幅度提高Auto Layout性能,使滑动屏幕时达到满帧。 在WWDC 2018 What's New in Cocoa Touch苹果的工程师说了iOS 12对Auto Layout优化后的表现。 算法
iOS 11
中视图嵌套的数量的性能快成指数级别增加了,在
iOS 12
中已经基本和手写frame布局的性能相似了。
从iOS 6
到iOS 12
,苹果也在不断的优化Auto Layout
的性能,同时为开发者提供更简洁的API
,若是你还在使用frame
手写布局,不妨试试Auto Layout
。下面我将介绍iOS
中几种经常使用的布局方法。bash
二、Auto Layout各个版本不一样用法
如我要设置一个宽高为120,居中显示的View,效果以下图: app
UIView *centerView = [[UIView alloc] init];
centerView.backgroundColor = [UIColor redColor];
[self.view addSubview:centerView];
CGFloat width = self.view.frame.size.width;
CGFloat height = self.view.frame.size.height;
[centerView setFrame:CGRectMake(width / 2 - (60), height / 2 - (60), 120, 120)];
复制代码
centerView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *consW = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0
constant:120.0
];
NSLayoutConstraint *consH = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeHeight
multiplier:0
constant:120.0
];
NSLayoutConstraint *consX = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0
];
NSLayoutConstraint *consY = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0
];
[self.view addConstraints:@[consW,consH,consX,consY]];
复制代码
centerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[centerView(120)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[centerView(120)]" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
复制代码
__weak typeof (self) weakSelf = self;
[centerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(120, 120));
make.center.equalTo(weakSelf.view);
}];
复制代码
let centerView:UIView = UIView.init()
view.addSubview(centerView)
centerView.backgroundColor = UIColor.red
centerView.snp.makeConstraints { (make) in
make.width.equalTo(120)
make.height.equalTo(120)
make.center.equalTo(view)
}
复制代码
let centerView:UIView = UIView.init()
view.addSubview(centerView)
centerView.backgroundColor = UIColor.red
centerView.translatesAutoresizingMaskIntoConstraints = false
centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
centerView.widthAnchor.constraint(equalToConstant: 120).isActive = true
centerView.heightAnchor.constraint(equalToConstant: 120).isActive = true
复制代码
经过上面的代码对比,使用frame
手写布局只要几行代码就搞定了,使用NSLayoutConstraint
语法和VFL
语法是最复杂的,尤为是NSLayoutConstraint
语法要用30多行代码才能是想一样的效果,代码行数越多出错的几率也就成正比上升,因此这就是不少开发者不肯使用Auto Layout
(或者说不肯意使用系统提供API来实现)的缘由之一吧。框架
若是你的App
要兼容iOS 9
如下的各个版本,建议使用Masonry,若是只兼容iOS 9以上的版本,建议使用SnapKit或者系统提供的NSLayoutAnchor API,毕竟Masonry
这个库已经2年没有更新了。iview
在这里我推荐优先使用NSLayoutAnchor
,第三方的开源库随时都面临着一些问题:ide
iOS
系统版本的更新形成的适配和兼容问题,若是是开源代码要等到苹果发布新版本,代码的做者再作兼容和适配三、
Auto Layout
的生命周期
前面说到苹果的Auto Layout
是基于Cassowary
算法的,苹果在此基础上提供了一套Layout Engine
引擎,由它来管理页面的布局,来完成建立、更新、销毁等。
在APP
启动后,会开启一个常驻线程来监听约束变化,当约束发生变化后会出发Deffered Layout Pass
(延迟布局传递),在里面作容错处理(若有些视图在更新约束时没有肯定或缺失布局申明),完成后进入约束监听变化的状态。
当下一次刷新视图(如调用layoutIfNeeded()
)时,Layout Engine
会从上到下调用layoutSubviews()
,而后经过Cassowary
算法计算各个子视图的大小和位置,算出来后将子视图的frame
从layout Engine
里拷贝出来,在以后的处理就和手写frame
的绘制、渲染的过程同样了。使用Auto Layout
和手写frame
多的工做就在布局计算上。
四、
NSLayoutAnchor
经常使用属性
对于NSLayoutAnchor
的一些经常使用属性,经过其命名就能看出来其做用,这里不作赘述,若是想了解更多请查阅Apple Developer NSLayoutAnchor。
五、Auto Layout几个更新约束的方法
setNeedsLayout: 告知页面须要更新,可是不会马上开始更新。执行后会马上调用layoutSubviews
。
layoutIfNeeded: 告知页面布局马上更新。因此通常都会和setNeedsLayout
一块儿使用。若是但愿马上生成新的frame
须要调用此方法,利用这点通常布局动画能够在更新布局后直接使用这个方法让动画生效。
layoutSubviews: 更新子View
约束
setNeedsUpdateConstraints:须要更新约束,可是不会马上开始
updateConstraintsIfNeeded:马上更新约束
updateConstraints:更新View
约束
六、
NSLayoutAnchor
使用注意事项
NSLayoutAnchor
为视图添加约束时必定要先把translatesAutoresizingMaskIntoConstraints
设置false
centerView.translatesAutoresizingMaskIntoConstraints = false
复制代码
safeAreaLayoutGuide
适配iPhone X
等机型时要对iOS 11
以前的系统作适配,不然会致使低版本系统上程序Crashif #available(iOS 11.0, *) {
tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
} else {
tableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
}
复制代码
isActive
为true
let centerX: NSLayoutConstraint = centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
centerX.isActive = true
复制代码
leadingAnchor
不要和 leftAnchor
混用centerView.leadingAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
复制代码
centerView.leftAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
复制代码
以上2种写法,在编译时不会出现任何问题,可是在运行时就会报错,并会致使程序Crash,官方的说法是:
While the NSLayoutAnchor class provides additional type checking, it is still possible to create
invalid constraints. For example, the compiler allows you to constrain one view’s leadingAnchor
with another view’s leftAnchor, since they are both NSLayoutXAxisAnchor instances. However,
Auto Layout does not allow constraints that mix leading and trailing attributes with left or right
attributes. As a result, this constraint crashes at runtime.
复制代码
同理,trailingAnchor
和rightAnchor
也不能混用。
如我要修改一个UIView
的宽度: 经过代码添加约束,可把UIView
的宽度设置类属性,而后在须要的地方修改constant
的参数,而后在刷新约束便可,代码以下:
var centerView: UIView!
var centerWidth: NSLayoutConstraint!
复制代码
self.centerView = UIView.init()
view.addSubview(self.centerView)
self.centerView.backgroundColor = UIColor.red
self.centerView.translatesAutoresizingMaskIntoConstraints = false
self.centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
self.centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
self.centerWidth = self.centerView.widthAnchor.constraint(equalToConstant: 120)
self.centerWidth.isActive = true
self.centerView.heightAnchor.constraint(equalToConstant: 120).isActive = true
复制代码
self.centerWidth.constant = 250
weak var weakSelf = self
UIView.animate(withDuration: 0.35, animations: {
weakSelf?.centerView.superview?.layoutIfNeeded()
}) { (finished) in
}
复制代码
效果以下:
若是是xib
或者storyboard
,那就更简单了,直接摁住键盘control
键,拖到对应的类里,而后在须要的地方修改约束并刷新便可。操做以下:
在开发中,咱们会遇到一些需求要求根据UIView
的宽高比来设置约束,如通常状况下显示视频的宽高比是16:9,经过代码设置宽高好比下:
centerView.heightAnchor.constraint(equalToConstant: 90).isActive = true
centerView.widthAnchor.constraint(equalTo: centerView.heightAnchor, multiplier: 16 / 9).isActive = true
复制代码
七、
Auto Layout
自适应UITableViewCell
高度使用
rowHeight
设置高度 通常状况下,若是UITableView
的每一个Cell
高度是固定的咱们能够直接指定一个值便可,若是没有设置UITableView
的高度,系统会默认设置rowHeight
高度是44。
tableview.rowHeight = 44;
复制代码
也能够经过UITableViewDelegate的代理来设置UItableView的高度。
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
复制代码
若是经过手动计算每一个UItableViewCell
的高度,也在这个代理中实现,经过计算返回每一个UItableViewCell
的高度。
estimatedRowHeight
设置高度 UItableView
继承自UIScrollView
,UIScrollView
的滚动须要设置其contentSize
后,而后根据自身的bounds、contentInset、contentOffset
等属性来计算出可滚动的长度。而UITableView
在初始化时并不知道这些参数,只有在设置了delegate
和dataSource
以后,根据建立的UITableViewCell
的个数和加载的UITableViewCell
的高度以后才能算出可滚动的长度。
在使用Auto Layout
自适应UITableViewCell
高度时应提早设置一个估算值,固然这个估算值越接近真实值越好。
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
复制代码
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
复制代码
如上图所示:这个界面就是用Auto Layout + estimatedRowHeight
完成自适应高度的,在添加约束时要按照从上到下的书讯设置每个UIView
的顶部(top
)到上一个的视图底部的(bottom
)距离,同时要计算UITableViewCell
内部全部控件的高度。那么问题来了,用户发布的内容详情没有获得数据以前时没办法算出其高度的,此处能够先给内容文字UILabel
设置一个默认高度,而后让其根据内容填充自动计算高度:
topicInfoLab.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true;
topicInfoLab.font = UIFont.init(name: "Montserrat-SemiBold", size: 12)
topicInfoLab.numberOfLines = 0
复制代码
若是用户发布内容没有图片,直接设置发布内容UILabel距离UITableView距离底部的约束距离便可;
detailsLab.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
复制代码
若是用户发布的内容有图片,那么在计算出每张图片的位置和大小以后,必定要给最后一张图片设置距离UItableViewCell
底部(bottom
)的约束距离。
for(idx, obj) in imageArray.enumerated() {
//.....计算图片的大小和位置
if idx == imageArray.count - 1 {
//设置最后一张图片距离底部的约束
photo.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
}
}
复制代码
实现思路如上图所示,具体实现的请看代码
八、
Compression Resistance Priority
和Hugging Priority
使用
Compression Resistance Priority
和 Hugging Priority
在实际使用中每每配合使用,分别处理在同义水平线上多个view之间内容过少和内容过多而形成的互相压挤的状况。
Hugging Priority
的意思就是自包裹的优先级,优先级越高,则优先将尺寸按照控件的内容进行填充。
Compression Resistance Priority
,意思是说当不够显示内容时,根据这个优先级进行切割。优先级越低,越容易被切掉。
ContentHuggingPriority |
表示当前的UIView 的内容不想被拉伸 |
---|---|
ContentCompressionResistancePriority |
表示当前的UIView 的内容不想被收缩 |
默认状况下: HuggingPriority = 250 |
默认状况下: CompressionResistancePriority = 750 |
如设置2个UILabel
的拉伸优先级可以使用代码:
fristLab.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .horizontal)
secondLab.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
复制代码
九、总结
本文主要分享了苹果Auto Layout
的几种实现方法和注意事项,对于Auto Layout
在实际开发中的使用是采用纯代码、仍是xib
+ 代码,仍是storyboard
+ 代码,仍是xib
+ storyboard
+ 代码的方式实现,主要看团队的要求、我的的习惯,以及App
的繁琐程度。 对于Auto Layout
在视图上的使用,我的建议若是UI比较简单或者单一的界面可以使用Auto Layout
,若是UI的操做或刷新很复杂的界面,建议仍是frame
+ 手动布局的方式。
友情连接:
Apple Developer High Performance Auto Layout