创建一个带有分配百分比值的 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 点:
我正在尝试创建一个自定义 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 点: