AutoLayout 相关知识点
1.Auto Layout
苹果公司在 iOS6 开始引入,在 iOS9 开始苹果公司推出了在 Auto Layout 基础上模仿前端Flexbox布局思路的UIStackView工具。极大地减少在约束关系设置上所做的重复操作,提升页面布局的体验。
Auto Layout 依赖 Cassowary 算法。
Auto Layout 生命周期
还包含了布局在运行时的声明周期等一整套布局引擎系统,用来统一管理布局的创建、更新和销毁。叫做Layout Engine,是Auto Layout的核心,主导着整个界面布局。
- Layout Engine
Layout Engine会将视图、约束、优先级、固定大小通过计算转换成最终的大小和位置。Layout Engine在碰到约束变化后会重新计算布局,获取到布局后,调用superview.setNeedLayout(),然后进入 Deferred Layout Pass。 - Deferred Layout Pass 主要作用是容错处理。如果有些视图在更新约束时没有确定或缺失布局声明的话,会现在这里做容错处理。
- 接下来Auto Layout会从上到下调用layoutubviews(),通过Cassowary算法计算各个子视图的位置,算出来后将子视图的frame从Layout Engine里拷贝出来。
之后过程就跟手写布局的绘制、渲染过程一样了,就是比手写布局多了个计算过程。
Auto Layout 性能
Cassowary算法是以高效的界面线性方程求解算法被提出来的,它解决的是界面的线性规划问题,而线性规划问题的解法是 Simplex 算法。这个算法没有指数线性复杂度的。而Cassowary算法又是在 Simplex 算法基础上对界面关系方程进行了高效的添加、修改更新操作,不会带来时间复杂度成指数级增长的问题。
在iOS12之前,很多约束变化时都会重新创建一个计算引擎将约束关系重新加进来,然后重新计算。结果就是,涉及到的约束关系变多时,新的计算引擎需要重新计算,最终导致计算量呈指数级增加。
iOS12更多地利用了Cassowary算法的界面更新策略,使其真正完成了高效的界面线性策略计算。
相关方法
1.intrinsicContentSize
intrinsicContentSize
Override this method to tell the layout system that there is something it doesn't natively understand in this view, and this is how large it intrinsically is.
固有内容大小:有些控件能通过自己显示的内容计算出需要的Size,这个自动计算出来size就叫该控件的固有内容大小。这个大小是和需要显示的内容相关的。UIButton,UILabel就是具有固有内容大小属性的控件。UIButton可以根据它的title字符串长度和需要显示的image来计算需要的Size,UILabel可以根据它的text来计算。
-(CGSize)intrinsicContentSize
就是根据它的text、attributedText和preferredMaxLayoutWidth等来计算出它的size。
当视图内容改变时,可以调用- (void)invalidateIntrinsicContentSize
方法来让AutoLayout在下次布局时重新计算。
在YYLabel的实现中,label中是调用preferredMaxLayoutWidth来处理的。
- (CGSize)intrinsicContentSize {
if (_preferredMaxLayoutWidth == 0) {
YYTextContainer *container = [_innerContainer copy];
container.size = YYTextContainerMaxSize;
YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];
return layout.textBoundingSize;
}
CGSize containerSize = _innerContainer.size;
if (!_verticalForm) {
containerSize.height = YYTextContainerMaxSize.height;
containerSize.width = _preferredMaxLayoutWidth;
if (containerSize.width == 0) containerSize.width = self.bounds.size.width;
} else {
containerSize.width = YYTextContainerMaxSize.width;
containerSize.height = _preferredMaxLayoutWidth;
if (containerSize.height == 0) containerSize.height = self.bounds.size.height;
}
YYTextContainer *container = [_innerContainer copy];
container.size = containerSize;
YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];
return layout.textBoundingSize;
}
- 只有部分视图具有intrinsicContentSize。
- UIView没有IntrinsicContentSize;默认IntrinsicContentSize是返回(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric),UIViewNoIntrinsicMetric是UIView中定义的一个常量,值为-1,表示没有内在内容大小。当然我们也可以自定义
- Sliders只能定义width。Sliders的height拥有IntrinsicContentSize;
- UILabel、UIButton、UISwitch、UITextField的IntrinsicContentSize同时存在width、height;
- UITextView、UIImageView的IntrinsicContentSize是动态变化的;
原理:视图的intrinsicContentSize本质上还是通过约束来实现的,AutoLayout在每个坐标轴方向设置两个约束,分别是:contentHugging(内容吸附,默认250)、compressionResistance(压缩阻力,默认750)。
当使用Interface Builder布局时,默认会把UIImageView、UILabel的contenthugging优先级调整为251。Apple这么做原因应该是:大部分情况下,当与UITextField等共存,当需要拉伸时,一般是希望拉伸后者。
2.preferredMaxLayoutWidth
用来约束换行操作,当内容超过约束区域时就会自动换行,并且更新约束布局,preferredMaxLayoutWidth 就是告诉最大的参考宽度
// Support for constraint-based layout (auto layout)
// If nonzero, this is used when determining -intrinsicContentSize for multiline labels
@property(nonatomic) CGFloat preferredMaxLayoutWidth NS_AVAILABLE_IOS(6_0);
多线标签的首选最大高度,在使用systemLayoutSizeFittingSize计算动态高度时,如果是计算多行UILabel,并且width不是固定的话(自动布局适配屏幕就是不固定,写死的数值算固定),需要设置label的preferredMaxLayoutWidth属性,否则计算的结果会有偏差。
preferredMaxLayoutWidth未设置值时,会调用intrinsicContentSize多次,待计算好preferredMaxLayoutWidth后,才能返回准确的intrinsicContentSize
3.systemLayoutSizeFittingSize
根据当前的约束返回视图的最优大小 targetSize:获得一个视图尽可能小,指定UILayoutFittingCompressedSize不变。获得尽可能大的一个视图,指定UILayoutFittingExpandedSize不变。
值得一提的是,在使用[view systemLayoutSizeFittingSize:]时,要注意尽量确保view的constraints的完整性,这样参数UILayoutFittingCompressedSize和UILayoutFittingExpandedSize得到的结果是一样的。
4.sizeThatFits
和sizeToFit
- (CGSize)sizeThatFits:(CGSize)size; // return 'best' size to fit given size. does not actually resize view. Default is return existing view size
- (void)sizeToFit; // calls sizeThatFits: with current view bounds and changes bounds size.
调用sizeThatFits:并不改变view的size,它只是让view根据已有content和给定size计算出最合适的view.size。
简单来说,sizeToFit等价于:
// calls sizeThatFits
CGSize size = [self sizeThatFits:self.bounds.size];
// change bounds size
CGRect bounds = self.bounds;
bounds.size.width = size.width;
bounds.size.height = size.width;
self.bounds = bounds;
针对 frame 布局
5.Content Hugging Priority
该优先级表示一个控件抗被拉伸的优先级。优先级越高,越不容易被拉伸,默认是251.
6.Content Compression Resistance Priority:
该优先级和上面那个优先级相对应,表示一个控件抗压缩的优先级。优先级越高,越不容易被压缩,默认是750.
总结:
- IntrinsicContentSize是Auto Layout的输入源,而FittingSize是Auto Layout的输出结果。IntrinsicContentSize是转化为约束集合的一部分,参与Auto Layout的布局计算。而一个视图的FittingSize是基于子视图的约束集合、内容、来计算出视图本身的尺寸。
- UIStackView看起来好像是具有IntrinsicContentSize,实际上并不是,它是基于FittingSize。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论