iOS Auto Layout学习笔记

一、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优化后的表现。 算法

WWDC 2018 What's New in Cocoa Touch
从图上能够看出, iOS 11中视图嵌套的数量的性能快成指数级别增加了,在 iOS 12中已经基本和手写frame布局的性能相似了。

iOS 6iOS 12,苹果也在不断的优化Auto Layout的性能,同时为开发者提供更简洁的API,若是你还在使用frame手写布局,不妨试试Auto Layout。下面我将介绍iOS中几种经常使用的布局方法。bash

二、Auto Layout各个版本不一样用法

      如我要设置一个宽高为120,居中显示的View,效果以下图: app

AutoLayoutdemo3.png

一、用frame手写布局
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)];
复制代码
二、iOS 6提供的NSLayoutConstraint语法添加约束
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]];

复制代码
三、用VFL语法
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]];
复制代码
四、使用第三方开源框架MasonrySnapKit
__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)
 }
复制代码
五、使用iOS 9以后Apple提供的NSLayoutAnchor
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算法计算各个子视图的大小和位置,算出来后将子视图的framelayout Engine里拷贝出来,在以后的处理就和手写frame的绘制、渲染的过程同样了。使用Auto Layout和手写frame多的工做就在布局计算上。

四、NSLayoutAnchor经常使用属性

  • leadingAnchor
  • trailingAnchor
  • leftAnchor
  • rightAnchor
  • topAnchor
  • bottomAnchor
  • widthAnchor
  • heightAnchor
  • centerXAnchor
  • centerYAnchor
  • firstBaselineAnchor
  • lastBaselineAnchor

对于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以前的系统作适配,不然会致使低版本系统上程序Crash
if #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
 }
复制代码
三、设置约束后要将其激活,即设置isActivetrue
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.
复制代码

同理,trailingAnchorrightAnchor也不能混用。

五、如何刷新某个约束

      如我要修改一个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
            
}
复制代码

效果以下:

layoutDemo5.gif

若是是xib或者storyboard,那就更简单了,直接摁住键盘control键,拖到对应的类里,而后在须要的地方修改约束并刷新便可。操做以下:

AutoLayoutdemo6.gif

六、设置宽高比

      在开发中,咱们会遇到一些需求要求根据UIView的宽高比来设置约束,如通常状况下显示视频的宽高比是16:9,经过代码设置宽高好比下:

centerView.heightAnchor.constraint(equalToConstant: 90).isActive = true
 centerView.widthAnchor.constraint(equalTo: centerView.heightAnchor, multiplier: 16 / 9).isActive = true
复制代码

layoutDemo7.png

七、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在初始化时并不知道这些参数,只有在设置了delegatedataSource以后,根据建立的UITableViewCell的个数和加载的UITableViewCell的高度以后才能算出可滚动的长度。

在使用Auto Layout自适应UITableViewCell高度时应提早设置一个估算值,固然这个估算值越接近真实值越好。

tableView.rowHeight = UITableView.automaticDimension
 tableView.estimatedRowHeight = 200
复制代码
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 200    
 }
复制代码

Autolayoutdemo2.png

      如上图所示:这个界面就是用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
 }
}
复制代码

layoutDemo8.png

实现思路如上图所示,具体实现的请看代码

八、 Compression Resistance PriorityHugging Priority使用

Compression Resistance PriorityHugging 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 + 手动布局的方式。


本文demo,请戳这里

友情连接:

深刻剖析Auto Layout,分析iOS各版本新增特性

Auto Layout 是怎么进行自动布局的,性能如何?

Apple Developer High Performance Auto Layout

Apple Develope NSLayoutConstraint

WWDC 2018 What's New in Cocoa Touch

相关文章
相关标签/搜索