重学AutoLayout (2) -- UIStackView

重学AutoLayout (2) -- UIStackView

StackView 的重点

  1. UIStackView 只是一个容器!!!(它本身没有intrinsic content size)
  2. UIStackView中不同的 distributions 属性工作的原理不一样!!!(fill & fillEqual 表现是不同的!)
  3. 加入到UIStackView中的元素必须拥有Intrinsic Content Size

ScrollView中嵌套StackView

其中的几个关键:

  1. ScrollView的Anchor 配置 ScrollView的 ViewPort
  2. ScrollView内部的StackView的Anchor也需要 Pin到 ScrollView的大小
  3. StackView中的Row必须有IntrinsicContentSize
  4. 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的使用总结

  1. 如果我们Pin一个StackView到某些边距的side时, 注意StackView中内容的变化(与center StackView的结果肯定不同)

  2. UIStackView管理的对象必须拥有Intrinsic Size, 因此如果要引入一些自定义对象, 需要重写IntrinsicContentSize方法.

  3. 在轴向上,除了stackView.distribution = .fillEqually以外, 其他的 distribution type, stackview 使用arrangedViewsintrinsicContentSize来计算stack axis的尺寸!!! (这样我们大概能计算出 StackView 在没有其他的外部约束时, 轴向的length).

    1. fillEqually 会使得所有arrangeViews在轴向上拥有相同尺寸的约束!!
  4. 在交叉轴上, 除了 stackView.alignment = .fill 以外, 其他的 alignment type, stackview 也是使用 arrangedViewsintrinsicContentSize 的交叉轴的大小. (这样我们大概能计算出 StackView 在没有其他的外部约束时, 交叉轴向的length).

    1. .alignment = .fill时, stack 视图将拉伸其所有管理的视图来匹配其交叉轴中 arrangeViews 的最大的固有尺寸
  5. 在使用StackView时, 除了管理有效内容以后, 也可以通过Anchor增加一些 StackSpacerView, 帮助我们修改不同arrangeSubviews 之间的间隔!!!

  6. 使用StackView时, 如果需要在交叉轴拉伸StackView, 有两种常见的方式, 这样required priority 的Anchor能 Override Intrinsic Size的约束:

    1. 直接给StackView设置widthAnchor
    2. 给ArrangeView设置一个交叉轴的Anchor, Anchor 需要在 outside View上.
  7. 嵌套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
    }
}
复制代码

参考

  1. www.cnblogs.com/tieria/p/45…
  2. www.jianshu.com/p/633e238b2…
  3. github.com/HChong3210/…

猜你喜欢

转载自juejin.im/post/7079717851241644045