UIStackView 是 Apple 在 iOS9 推出的一套 API,它能够很好地减轻手动写或拖 constraint 带来的重复繁琐的工做,也能够自动化的处理排列和元素个数的变化。git
正因为其 iOS9+ 的门槛,而国内 app 广泛要兼容 iOS8,再加上 UIStackView 的真正威力实际上是 Storyboard, 即使有 FDStackView 这样的黑科技能够下降引入门槛,团队仍是倾向于使用纯 Masonry/SnapKit 的方式来实现 Autolayout。github
UIStackView 顾名思义,就是一个视图堆栈 ,换句话说:他是一个容器。这类容器型的控件咱们不禁联想到 UITableView,UICollectionView。相比于这两个传统容器,UIStackView 的定位是这样的:swift
UIStackView 和传统容器类另外一个区别是他本身虽然继承自 UIView,但它自己不能自我渲染,好比他的 backgroundColor 是无效的,因此它注定要和 UIView 相辅相成的进行工做。它可以帮助 UIView 来处理 子View 的位置和大小等布局问题。数组
然而虽然说是处理布局,但它也不能彻底代替 constraint,他能作的,很少很多,就是一个堆栈能作到的事,除此以外,好比 子View 的本身内在 size,或是 CHP(Content Hugging Priority),CRP(Content Resistance Priority),更包括 UIStackView 自己的布局,都是离不开手写约束。因此一个好的 Autolayout 封装库仍是须要的。markdown
要说其定位,应该就是介于 手写约束 和 UITableView/UICollectionView 之间的工具。就像 iPad 是 笔记本电脑 和 手机 之间的设备同样。它谁也代替不了,可是它有自信的领域,那就是手写 Constraint 很累,可是用 UITableView/UICollectionView 又以为很笨重的场合。app
好比下面这个若是用原生实现,就能够看作是这些 UIStackView 的嵌套:工具
在极简状况下,引入 UIStackView 的 view hierarchy 是一个这样的情况:oop
要实现这个简单的模型,首先须要建立一个 UIStackView:布局
let stackView = UIStackView() 复制代码
而后把他加到父层的 UIView 上ui
view.addSubview(stackView)
复制代码
接着,把 子View 实例加到 UIStackView 里,这里调用的不是传统的 addSubview
,而是
stackView.addArrangedSubview(subView1)
stackView.addArrangedSubview(subView2)
复制代码
这时 UIStackView 的 arrangedSubviews 就有值了
open var arrangedSubviews: [UIView] { get } 复制代码
arrangedSubviews
和 subviews
的顺序意义是不一样的:
subviews
:它的顺序其实是图层覆盖顺序,也就是视图元素的 z轴arrangedSubviews
:它的顺序表明了 stack 堆叠的位置顺序,即视图元素的x轴和y轴实战中,我用这样一个扩展来批量添加:
extension UIStackView { func addArrangedSubviews(_ views: [UIView?]) { views.compactMap({ $0 }).forEach { addArrangedSubview($0) } } } 复制代码
既然 UIStackView 是 UIView,意味着便可以调用 addSubview
,也能够 addArrangedSubview
,他们的关系是什么样的呢?
addSubview
,调用 arrangedSubviews
会自动 addSubview
removeFromSuperview
,则 arrangedSubviews
也会同步移除removeArrangedSubview
, 不会触发 removeFromSuperview
,它依然在视图结构中UIStackView 有几个重要的属性,这也是咱们惟一须要控制的开关,那解决一个页面的布局问题,就转换成如何用这几个有限的开关来描述这个页面的元素。
horizontal
水平方向 (默认)vertical
垂直方向定义:
The layout that defines the size and position of the arranged views along the stack view’s axis.
描述和 axis
方向一致的元素之间的布局关系
.fill
(默认) 根据compression resistance和hugging两个 priority 布局
.fillEqually
根据 等宽/高 布局
.fillProportionally
根据intrinsic content size按比例布局
equalSpacing
等间距布局,若是放不下,根据compression resistance压缩
.equalCentering
等中间线间距布局,元素间距不小于 spacing
定义的值, 若是放不下,根据compression resistance压缩
The alignment of the arranged subviews perpendicular to the stack view’s axis.
描述和 axis
垂直的元素之间的布局关系
.fill
(默认) 尽量铺满
.leading
当 axis
是 vertical
的时候,按 leading 方向对齐 等价于: 当 axis
是 horizontal
的时候,按 top 方向对齐
.top
当 axis
是 horizontal
的时候,按 top 方向对齐 等价于: 当 axis
是 vertical
的时候,按 leading 方向对齐
.trailing
当 axis
是 vertical
的时候,按 trailing 方向对齐 等价于: 当 axis
是 horizontal
的时候,按 bottom 方向对齐
bottom
当 axis
是 horizontal
的时候,按 bottom 方向对齐 等价于: 当 axis
是 vertical
的时候,按 trailing 方向对齐
.center
居中对齐
.firstBaseline
仅横轴有用, 按首行基线对齐
.lastBaseline
仅横轴有用, 按文章底部基线对齐
设置元素之间的边距值
决定了垂直轴若是是文本的话,是否按照 baseline 来参与布局。
若是打开则经过 layout margins 布局,关闭则经过 bounds
一、设置一个元素后面的边距
func setCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) 复制代码
二、获取一个元素后面的边距
func customSpacing(after arrangedSubview: UIView) -> CGFloat 复制代码
三、获取内部元素默认边距
class let spacingUseDefault: CGFloat 复制代码
四、获取相邻 View 之间的默认边距
class let spacingUseSystem: CGFloat 复制代码
可是须要注意的是,自定义边距是 iOS11+ 的特性,若是须要 iOS9 兼容, 须要引入一个hack的方案:
extension UIStackView { // How can I create UIStackView with variable spacing between views? func addCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) { if #available(iOS 11.0, *) { self.setCustomSpacing(spacing, after: arrangedSubview) } else { let separatorView = UIView(frame: .zero) separatorView.translatesAutoresizingMaskIntoConstraints = false switch axis { case .horizontal: separatorView.widthAnchor.constraint(equalToConstant: spacing).isActive = true case .vertical: separatorView.heightAnchor.constraint(equalToConstant: spacing).isActive = true } if let index = self.arrangedSubviews.firstIndex(of: arrangedSubview) { insertArrangedSubview(separatorView, at: index + 1) } } } 复制代码
UIStackView 的布局会动态的同步数组 arrangedSubviews
的变化。 变化包括:
注意:对于隐藏(isHidden)的处理,UIStackView 会自动把空间利用起来,至关于暂时的删去,而不像 Autolayout 通常不破坏约束的作法。
如何让一层一层的 StackView 能够和气相处呢? 答案就是约束完备:
即使你目前正使用某种 Autolayout 的封装,引入UIStackView 都是一个有效下降页面约束复杂度的方式。它让你能够用一个大局观去看待排版,而不是陷入每一个元素的约束细节里。最棒的是,它提供了更低的维护成本(好比茫茫约束中插入一个按钮)和更高的容错率(手写约束产生语义冲突)。
有同窗问实战用起来是什么感受。下面举一个小例子:
这是一个有翻译功能的聊天气泡,只需关注深灰色的区域
stackView.addArrangedSubviews([contentLabel,
translationLoadingSeparatorLine,
translationLoadingView])
复制代码
stackView.addArrangedSubviews([contentLabel,
translationResultTopSeparatorLine,
translationResultTextLabel,
translationResultBottomSeparatorLine,
translationResultBottomLabel])
复制代码
切换一个页面的布局方案,就是清空和重装对应的 stackView 就好了。 是否是优雅了一点?