Auto Layout 的经验之谈

Auto Layout 的本质原理

Auto Layout 的本质是用一些约束条件对元素进行约束,从而让他们显示在我们想让他们显示的地方。
约束主要分为以下几种:

  • 相对于父 view 的约束。如:距离上边距 10,左边距 10。
  • 相对于前一个元素的约束。如:距离上一个元素 20,距离左边的元素 5 等。
  • 对齐类约束。如:跟父 view 左对齐,跟上一个元素居中对齐等。
  • 相等约束。如:跟父 view 等宽。

使用快捷键快速添加约束


可以通过如图所示快捷键快速创建与父视图关系的约束

使用鼠标拖拽添加约束



按住control键从一个View拖至另一个View。
按住option键来切换是否需要margins
按住shift键可以多选,一次添加多个约束

Masonry

这是一个第三方的非常好用的开源框架,以前我以为Autolayout只能用xib了,否则代码量太大了,但是Masonry让用代码写Autolayout成为可能,而且非常强大,清晰好用,示例:

1
2
3
4
5
6
7
8
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

AutoresizingMark

当我们使用Autoresizing时,运行时会自动根据autoresizingMask的特性为view添加Constraint, 我们可以通过下面这个API来决定启用还是关闭这一特性(NO为关闭):
- (void)setTranslatesAutoresizingMaskIntoConstraints:(BOOL)flag NS_AVAILABLE_IOS(6_0);
这个属性在xib中默认是NO, 在不用xib时默认是YES, 也就是如果使用代码创建Constraint时,需要设置该属性为NO, Masonry自动帮我们做了这一操作,所以用Masonry不需要设置改属性。

Update Constraint 与 Update Frame

Update Constraint 是根据InterfaceBuilder上View的位置来改变约束的值
Update Frame 是根据约束的规则来改变View的frame,使View放置在正确的位置。

代码中改变约束的值

interface中的任何对象都可以连线到代码中,约束也不例外。
连线后改变NSLayoutConsraint的constant的值。

margins

iOS8上关于UIView的Margin新增了3个APIs

1
2
3
@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0); 
@property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0);
- (void)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0);

在iOS 8中,可以使用layoutMargins去定义view之间的间距,该属性只对AutoLayout布局生效。

因此AutoLayout中NSLayoutAttribute的枚举值有了相应的更新:

1
NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin
NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin
NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin
NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins
NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins
NS_ENUM_AVAILABLE_IOS(8_0),

对UIScrollView使用 Auto Layout

参考这篇文章

Size Classes

参考这篇文章
这篇

Content Hugging 和 Content Compression Resistance

这两个属性对有intrinsic content size的控件(例如button,label)非常重要。通俗的讲,具有intrinsic content size的控件自己知道(可以计算)自己的大小,例如一个label,当你设置text,font之后,其大小是可以计算到的。关于intrinsic content size官方的解释

Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.

UIView中关于Content Hugging 和 Content Compression Resistance的方法有:

1
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis

Hugging priority 确定view有多大的优先级阻止自己变大。

Compression Resistance priority确定有多大的优先级阻止自己变小。

很抽象,其实content Hugging就是要维持当前view在它的optimal size(intrinsic content size),可以想象成给view添加了一个额外的width constraint,此constraint试图保持view的size不让其变大:

view.width <= optimal size

此constraint的优先级就是通过上面的方法得到和设置的,content Hugging默认为250.

Content Compression Resistance就是要维持当前view在他的optimal size(intrinsic content size),可以想象成给view添加了一个额外的width constraint,此constraint试图保持view的size不让其变小:
view.width >= optimal size
此默认优先级为750.
这两个属性分别可以设置水平方向和垂直方向上的,而且一个默认优先级是250, 一个默认优先级是750. 因为这两个很有可能与其他Constraint冲突,所以优先级较低。

布局动画

动画是UI体验的重要部分,更改布局以后的动画也非常关键。只需要把layoutIfNeeded放到animation block中即可

1

[UIView animateWithDuration:0.5 animations:^{
    //change view's layoutconstraint
    [view layoutIfNeeded];
}];

容易出现的错误

因为涉及约束问题,因此约束模型下的所有可能出现的问题这里都会出现,具体来说包括两种:

  • Ambiguous Layout 布局不能确定
  • Unsatisfiable Constraints 无法满足约束

布局不能确定指的是给出的约束条件无法唯一确定一种布局,也即约束条件不足,无法得到唯一的布局结果。这种情况一般添加一些必要的约束或者调整优先级可以解决。无法满足约束的问题来源是有约束条件互相冲突,因此无法同时满足,需要删掉一些约束。两种错误在出现时均会导致布局的不稳定和错误,Ambiguous可以被容忍并且选择一种可行布局呈现在UI上,Unsatisfiable的话会无法得到UI布局并报错。 对于不能确定的布局,可以通过调试时暂停程序,在debugger中输入

po [[UIWindow keyWindow] _autolayoutTrace]

来检查是否存在Ambiguous Layout以及存在的位置,来帮助添加条件。

使用原则

总结一下autolayout的使用原则:

  • 所添加的约束,必须是能够确定一个视图的位置和大小的
  • 所添加的约束,必须是可以计算的,能唯一得出 宽高或者边界值的
  • 所添加的约束,必须是不冲突的,否则会抛出异常
  • 所添加的约束,在storyboard上默认是相邻的最近的视图之间的约束
  • 所添加的约束,最好是以一个视图为基准添加,这样有利于修改,牵一发而动全身
  • 所添加的约束,最好模块化,一个模块内的约束修改不会影响到别的模块
  • Auto Layout 与 Auto Resizing 是可以相互兼容的。如果一个vuew内部没有添加约束,则view内使用的是Auto Resizing布局,可以手动改frame。如果添加了约束,则view内使用Auto Layout 布局
  • apple并不推荐我们删除约束再添加约束,会引发一些问题,推荐的做法是改变约束的值,cell复用时要注意。

Auto Layout进阶

AutoLayout 进阶 Demo,宽高比约束、比例约束、不等约束、视差约束、低优先级约束等高级用法,无需写码即可进行复杂页面布局,Demo 还动态模拟了各屏幕下的效果。来自百度知道 iOS 小组的内部分享,源码请戳:Auto-Layout-Showcase

这篇文章是百度知道团队最近对 UITableViewCell 利用 AutoLayout 自动高度计算和 UITableView 滑动优化的一个总结。
UITableView+FDTemplateLayoutCell,让高度计算这个事情变的前所未有的简单,也受到了很多星星的支持,github链接请戳我

百度知道团队还开源了UIView-FDCollapsibleConstraints,让一个View消失时跟View相关的约束也同时变为0
github链接请戳我