创建一个带有分配百分比值的 UIStackView

Creating a UIStackView with percentage values for distribution

我正在尝试创建一个自定义 UIStackView 函数,该函数采用百分比值来填充轴边界。我正在努力正确地实施这一点。我已经尝试了一些东西,但它不能正常工作。有什么办法可以实现吗?还是已经有内置的方法?

extension UIStackView {

    func setHeightPercentageFill(values: [CGFloat]) {
        guard values.count == arrangedSubviews.count else { return }
        axis = .vertical
        distribution = .fillProportionally
        for i in 0..<arrangedSubviews.count {
            arrangedSubviews[i].heightAnchor.constraint(equalToConstant: bounds.height * values[i]).isActive = true
        }
    }
}

预期用途(将每个子视图的高度设置为 20%、40%、30%、10%)

let stack = UIStackView(arrangedSubviews: [view1, view2, view3, view4])
addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.topAnchor.constraint(equalTo: topAnchor).isActive = true
stack.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
stack.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
stack.rightAnchor.constraint(equalTo: rightAnchor).isActive = true

stack.setHeightPercentageFill(values: [0.20, 0.40, 0.30, 0.10])

您的代码无法正常工作,因为 stackView 的大小取决于其子视图的固有内容大小。您有一个 chicken/egg 场景。 stackView 的高度取决于子视图的高度,子视图的高度取决于 stackView 的高度。你需要告诉 stackView 你想要的高度是什么。

将高度传递给 setHeightPercentageFill(values:) 对我有用:

extension UIStackView {
    func setHeightPercentageFill(values: [CGFloat], height: CGFloat) {
        guard values.count == arrangedSubviews.count else { return }
        axis = .vertical
        distribution = .fillProportionally
        for i in 0..<arrangedSubviews.count {
            arrangedSubviews[i].heightAnchor.constraint(equalToConstant: height * values[i]).isActive = true
        }
    }
}

注意我在storyboard中定义容器高度为128。您需要确保将 stackView 约束到的任何视图都具有固定高度。

class ViewController: UIViewController {

    lazy var view1: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .red
        return view
    }()

    lazy var view2: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .orange
        return view
    }()

    lazy var view3: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .blue
        return view
    }()

    lazy var view4: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .yellow
        return view
    }()

    @IBOutlet weak var container: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let stack = UIStackView(arrangedSubviews: [view1, view2, view3, view4])
        container.addSubview(stack)
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
        stack.leftAnchor.constraint(equalTo: container.leftAnchor).isActive = true
        stack.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
        stack.rightAnchor.constraint(equalTo: container.rightAnchor).isActive = true

        stack.setHeightPercentageFill(values: [0.20, 0.40, 0.30, 0.10], height: container.frame.size.height)
    }
}

有多种方法可以完成这项任务。您可能会发现这个更灵活一些...

您可以将排列的子视图的高度约束设置为堆栈视图高度的百分比,而不是调用函数来计算显式值。

情侣优势:

  • 当堆栈视图的框架发生变化时,可能是在设备旋转时,不需要重新计算任何东西
  • 您可以通过设置限制(使用 .heightAnchor 或 .bottomAnchor) 来控制堆栈视图的总高度排列的子视图之一的高度
  • 如果堆栈视图高度不恒定,您可以可靠地在 viewDidLoad() 中设置约束,而无需等待自动布局来确定堆栈视图的高度

这是一个完整的示例 - 它会在 viewDidAppear()viewWillTransition(to size:...) 上将 arrangedSubviews 的高度打印到调试控制台以确认:

extension UIStackView {
    func setHeightPercentageFill(values: [CGFloat]) {
        guard values.count == arrangedSubviews.count else { return }
        axis = .vertical
        spacing = 0
        distribution = .fill
        for i in 0..<arrangedSubviews.count {
            arrangedSubviews[i].heightAnchor.constraint(equalTo: heightAnchor, multiplier: values[i]).isActive = true
        }
    }
}

class StackPercentViewController: UIViewController {

    let stack: UIStackView = {
        let v = UIStackView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        let colors: [UIColor] = [.red, .green, .blue, .yellow]

        colors.forEach {
            let v = UIView()
            v.backgroundColor = [=10=]
            stack.addArrangedSubview(v)
        }

        view.addSubview(stack)

        // respect safe area
        let g = view.safeAreaLayoutGuide

        NSLayoutConstraint.activate([

            // constrain stack view Top / Leading / Trailing at 40-pts
            stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),

        ])

        // change "if true" to "if false" to test giving the stack view a height
        if true {
            // either give one arranged subview a height constraint
            if let v = stack.arrangedSubviews.first {
                v.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
            }
        } else {
            // or, constrain the stack view's height (.heightAnchor or .bottomAnchor)
            stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0).isActive = true
            //stack.heightAnchor.constraint(equalToConstant: 200.0).isActive = true
        }

        stack.setHeightPercentageFill(values: [0.20, 0.40, 0.30, 0.10])

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        checkHeights()
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: nil, completion: {
            _ in
            self.checkHeights()
        })
    }

    func checkHeights() {
        // print heights of arrangedSubviews to debug console
        stack.arrangedSubviews.forEach {
            print([=10=].frame.size.height)
        }
        print() // blank line in console
    }

}

结果...

第一个子视图高度限制为 20:

堆栈视图高度限制为200:

堆栈视图在所有 4 个边上限制为 40 点: