自动布局包裹视图

文章目录
  1. 1. 为什么没有这样的效果
  2. 2. 推断最大 布局 宽度
  3. 3. 调整 preferredMaxLayoutWidth
  4. 4. 创建属于你自己的 包裹view
  5. 5. 压缩 包裹
  6. 6. 其他参考文章

原文 Auto Layout and Views that Wrap
理解包裹文本和其他流动布局。
在视屏中,盒子里的面,当container resized 的时候,UILabel (或者 Mac 系统里面的 NSTextField) 包裹的文字的行为像这样:

video1

这篇文章将会讲解,当 container resized 的时候 UILabel 的行为会是这样的:
video2

为什么没有这样的效果

在自动布局中,view 有一个“固有尺寸”(intrinsic content size)的概念:宽和高(都有,或有其一,或都没有)刚好满足view 的大小。布局系统根据view的 “compression resistance”的 优先级,至少会给view 一个足够大的空间。
布局系统分别对待 width 和 height。
举例:如果一个 segmented control’s的文本很长以至于超出包裹view的宽度,它的固有高度仍然尝试满足 在view的高度。
这种效果对于已经定义size的 view 非常好,such as buttons, sliders, and small labels.
但是包裹的view 会有十分复杂的行为:他们的 width 和 height 相互关联,系统有的时候会让固有 width 照顾固有 height,反过来也是。
这种情况不能单纯的用 布局限制来解释
证明: 思考一个简化模型,忽略word 截断和复杂的文本内容。我们想要一个有固定面积的 view,
i.e. width × height = constant. 布局限制一定要这也样的格式:
attribute1 = multiplier × attribute2constant (为了让系统可以提供确定的执行效果). 没有啥方式使用第二个等式来表示第一个等式。因此也没有啥方式描述布局限制包裹的 view。
我们必须使用布局引擎计算出来的 width 和 height 作为自由变量。
最简单的方式–也是apple 使用的一种–是固定 width,然后让 height 自适应(取决label 的内容大小)

推断最大 布局 宽度

UILabel 和 NSTextField 有 preferredMaxLayoutWidth 这个属性。如果它 非 0,他就会作为label 的固有尺寸的最大宽度,当 label有超过 container View 容量的文字是,label可以适配到 这个 width 上。这个 label 将会返回一个更大的
固有 height。(如果 label 只有少量文字,那么label 的固有width 就回小于__preferredMaxLayoutWidth__)
iOS 上,UILabel 的 preferredMaxLayoutWidth__被设置为label 的width在 它显示在nib 上的时候(即使在 runtime 它的size 改变)
OS X 上, NSTextField 可选__preferredMaxLayoutWidth
,在布局之后。
如果 label 的有效空间可以改变,像最上面 video1里面的demo,或者如果container 可以resized (或者rotated),你就需要动态的改变 preferredMaxLayoutWidth

调整 preferredMaxLayoutWidth

为了动态的设置preferredMaxLayoutWidth, 需要重载 label 的父类方法:

1
2
 -[UIView layoutSubviews]
 -[NSView layout]

为了得到最上面video2的效果,要将__preferredMaxLayoutWidth__设置为有效的label
例子的布局,label 要有这几个限制:
layout
固定左右限制,使label占据整个水平空间,
代码里不要使用 数字 硬编码你的 constrain. 这会是你的布局十分脆弱。取而代替的, 可以利用布局系统来达到效果,需要做两步。
上面 video2里面,在label 中 使用下面的代码:

1
2
3
4
5
6
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat availableLabelWidth = self.label.frame.size.width;
self.label.preferredMaxLayoutWidth = availableLabelWidth;
[super layoutSubviews];
}

实现调用 [super layoutSubviews],将会计算label上的 constraints(因为他是一个间接的子view)相应的改变它的frame。此时 width 是有用的,但是height不是;height 用的是label 的固有height,它依赖 以前 preferredMaxLayoutWidth 的值。
现在我们知道了 label 的实际width,我们设置它的最大布局width. 在内部,在下一次查询label 的固有尺寸时, label的固有尺寸是无效的,而对于当前的 width会有 精确的 height。
所有布局信息就绪, 再次调用 [super layoutSubviews]
again.

创建属于你自己的 包裹view

WrapDemo
. 这个例子有一个 preferredMaxLayoutWidth 属性 that the superview sets, 和 一个共享布局方法 (-[MyWrappingView enumerateItemRectsForLayoutWidth:usingBlock:]
). 这个方法在 -intrinsicContentSize 中调用–通过preferredMaxLayoutWidth 计算基本尺寸。在 -layoutSubviews 中调用,算出那些 找色的item 的实际 size 和 位置
video3

压缩 包裹

最后,有很多次我们想把包裹view 和内容抱紧 contenthugging 的方式组合。
可以添加约束,以使它以中心为中心,并至少有一些距离,而不是固定 label 的leading和trailing空间位置。
Label shrink-wrap constraints
将其与上面的- layoutsubviews结合起来实现提供以下行为:
video4

边缘的空间是充足的,如果它大于或等于常数-它只会把标签推进去,它就不会把它拉出来。我们想要做的是找到标签可以占用的宽度,而不需要占用所有的宽度。
自动布局API只提供一种计算距离的方法:布局,然后测量。因此,为了找出标签的宽度,我们告诉它要变得非常宽(仔细选择优先级),把它放在外面,测量它,然后使用结果作为首选的最大布局宽度。该标签的内在尺寸将完成其余部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)layoutSubviews {
NSLayoutConstraint *labelAsWideAsPossibleConstraint =
[NSLayoutConstraint constraintWithItem:self.label
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:0
multiplier:1.0
constant:1e8]; // a big number
labelAsWideAsPossibleConstraint.priority =
[self.label contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
[self.label addConstraint:labelAsWideAsPossibleConstraint];

[super layoutSubviews];

CGFloat availableLabelWidth = self.label.frame.size.width;
self.label.preferredMaxLayoutWidth = availableLabelWidth;
[self.label removeConstraint:labelAsWideAsPossibleConstraint];

[super layoutSubviews];
}

所有例子代码 WrapDemo project on GitHub.
感谢Kevin Cathey在布局方面的帮助和见解。

总结:UILabel 的 preferredMaxLayoutWidth
当应用布局约束时,这个属性会影响标签的大小。在布局中,如果文本超出了该属性指定的宽度,则额外的文本将流向一个或多个新行,从而增加标签的高度。

其他参考文章

Apple: iOS UILabel
iOS: Multi-line UILabel in Auto Layout
Advanced Auto Layout Toolbox