重学AutoLayout (2) — UIStackView
StackView 的重点
- UIStackView 只是一个容器!!!(它本身没有
intrinsic content size
) - UIStackView中不同的
distributions
属性工作的原理不一样!!!(fill & fillEqual 表现是不同的!) - 加入到UIStackView中的元素必须拥有
Intrinsic Content Size
ScrollView中嵌套StackView
其中的几个关键:
- ScrollView的Anchor 配置 ScrollView的 ViewPort
- ScrollView内部的StackView的Anchor也需要 Pin到 ScrollView的大小
- StackView中的Row必须有
IntrinsicContentSize
- Row可以强制 horizontal 方向的约束 override
IntrinsicContent.width
{
func setupViews() {
navigationItem.title = "Scrollable"
let stackView = makeStackView(withOrientation: .vertical)
let scrollView = makeScrollView()
// ScrollView 内部是 stackView
scrollView.addSubview(stackView)
view.addSubview(scrollView)
for _ in 1...30 {
let row = RowView()
stackView.addArrangedSubview(row)
// 控制内部控件约束外部view, 强行拉伸宽度
row.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
// Pinning to the sides of view
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// Pinning scrollview
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}
// 自定义 raow
class RowView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
let titleLabel = makeLabel(withText: "Gapless Playback")
let onOffSwith = makeSwitch(isOn: true)
addSubview(titleLabel)
addSubview(onOffSwith)
// titleLabel
titleLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
// offSwitch
onOffSwith.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
onOffSwith.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true
}
// 内置 Intrinsic ContentSize -> 给StackView 提供参考!!!
override var intrinsicContentSize: CGSize {
return CGSize(width: 200, height: 31)
}
}
复制代码
StackView的使用总结
-
如果我们Pin一个StackView到某些边距的side时, 注意StackView中内容的变化(与center StackView的结果肯定不同)
-
UIStackView管理的对象必须拥有
Intrinsic Size
, 因此如果要引入一些自定义对象, 需要重写IntrinsicContentSize
方法. -
在轴向上,除了
stackView.distribution = .fillEqually
以外, 其他的distribution type
, stackview 使用arrangedViews
的intrinsicContentSize
来计算stack axis的尺寸!!! (这样我们大概能计算出 StackView 在没有其他的外部约束时, 轴向的length).fillEqually
会使得所有arrangeViews在轴向上拥有相同尺寸的约束!!
-
在交叉轴上, 除了
stackView.alignment = .fill
以外, 其他的alignment type
, stackview 也是使用arrangedViews
的intrinsicContentSize
的交叉轴的大小. (这样我们大概能计算出 StackView 在没有其他的外部约束时, 交叉轴向的length)..alignment = .fill
时, stack 视图将拉伸其所有管理的视图来匹配其交叉轴中 arrangeViews 的最大的固有尺寸
-
在使用StackView时, 除了管理有效内容以后, 也可以通过Anchor增加一些
StackSpacerView
, 帮助我们修改不同arrangeSubviews 之间的间隔!!! -
使用StackView时, 如果需要在交叉轴拉伸StackView, 有两种常见的方式, 这样required priority 的Anchor能 Override
Intrinsic Size
的约束:- 直接给StackView设置widthAnchor
- 给ArrangeView设置一个交叉轴的Anchor, Anchor 需要在 outside View上.
-
嵌套StackView的设计方案, 一定是从内到外结合CRPCHP!!!! 具体可以参考 Apple官方的
Auto Layout CookBook
下面是一个StackSpacerView
的实例:
// 创建一个 spacerView!!! 专门用于增加控件
public func makeSpacerView(height: CGFloat? = nil) -> UIView {
let spacerView = UIView(frame: .zero)
// 增加它的 heightAchor 并且是 breakable()
if let height = height {
spacerView.heightAnchor.constraint(equalToConstant: height).setActiveBreakable()
}
spacerView.translatesAutoresizingMaskIntoConstraints = false
return spacerView
}
// 这里的服务, 表示当 priority 是 breakable 服务
public extension NSLayoutConstraint {
@objc
func setActiveBreakable(priority: UILayoutPriority = UILayoutPriority(900)) {
self.priority = priority
isActive = true
}
}
复制代码