iOS 具有按比例填充的子视图和固有宽度的 StackView 未按预期运行
iOS StackViews with proportionally filled Subviews and intrinsic width not behaving as expected
在这篇文章之后,我试图在 StackViews 上按比例调整大小。
https://spin.atomicobject.com/2017/02/07/uistackviev-proportional-custom-uiviews/
假设是通过覆盖 intrinsicContentSize 我们可以指定一个新数字,它将计算出子视图大小的比例并相应地调整视图大小。
当我重复实施时,出现了一些奇怪的行为。比率保留,但最后一个项目被拉伸以占据剩余的 space,而不是项目在父视图的整个宽度上缩放(见下图)。
代码:
class GuageSection: UIView {
var width: Double = 1.0
override var intrinsicContentSize: CGSize {
return CGSize(width: width, height: 1.0)
}
}
这样使用
var guageWrapper = UIStackView()
guageWrapper.distribution = .fillProportionally
let guageSection = GuageSection()
guageSection.width = category.range // Currently Doubles ranging between 1.0 and 1.5
guageWrapper.addArrangedSubview(guageSection)
我试过使用 translateAutoResizingMaskInConstraints 属性 和其他一些东西,但似乎没有什么可以改变这种行为。
如果有人在正确方向上的一个好点之前看到过这种行为,我们将不胜感激。
我不知道这是否是 "bug",但是...看来 UIStackView
在 .fillProportionally
及其初始布局计算方面存在问题。
如果 .spacing
是 0
(零),.fillProportionally
似乎按记录工作。如果 .spacing
是 非零 ,我们会看到问题。
所以,试试这个...用 0
的间距初始化堆栈视图,然后:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guageWrapper.spacing = 2
}
当然,您需要引用 guageWrapper
,因此请将其创建为 class 级别的变量。
编辑:
我用堆栈视图创建了一个示例作为自定义 UIView
的一部分。
使用 1.0, 2.0, 1.0, 1.0, 1.5
的 "intrinsic widths" 数组,结果如下:
一切都是通过代码完成的(不需要 @IBOutlets
),因此您应该能够 运行 通过添加新的视图控制器并将其自定义 class 设置为 GuageTestViewController
:
//
// GuageTestViewController.swift
//
// Created by Don Mag on 2/28/19.
//
import UIKit
class GuageSection: UIView {
let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.numberOfLines = 0
v.font = UIFont.systemFont(ofSize: 14.0)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let colorView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
var width: Double = 1.0
override var intrinsicContentSize: CGSize {
return CGSize(width: width, height: 1.0)
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
self.addSubview(colorView)
self.addSubview(label)
NSLayoutConstraint.activate([
colorView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
colorView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
colorView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0.0),
colorView.heightAnchor.constraint(equalToConstant: 10.0),
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
label.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0.0),
label.widthAnchor.constraint(equalTo: colorView.widthAnchor, constant: 0.0),
label.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 2.0),
])
}
}
class GuageView: UIView {
var pStack = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
self.backgroundColor = UIColor(red: 41.0 / 255.0, green: 59.0 / 255.0, blue: 78.0 / 255.0, alpha: 1.0)
pStack.translatesAutoresizingMaskIntoConstraints = false
pStack.axis = .horizontal
pStack.alignment = .fill
pStack.distribution = .fillProportionally
pStack.spacing = 0
addSubview(pStack)
let labels = [
"Low", "Ideal", "Pre-High", "High", "Very High"
]
let rgbVals = [
[252, 191, 127],
[ 79, 197, 140],
[252, 191, 127],
[249, 129, 131],
[217, 92, 98],
]
let widths = [
1.0, 2.0, 1.0, 1.0, 1.5
]
for i in 0..<labels.count {
let v = GuageSection()
v.translatesAutoresizingMaskIntoConstraints = false
v.label.text = labels[i]
v.width = widths[i]
let rgb = rgbVals[i].compactMap { CGFloat([=11=]) / 255.0 }
v.colorView.backgroundColor = UIColor(red: rgb[0], green: rgb[1], blue: rgb[2], alpha: 1.0)
v.label.textColor = v.colorView.backgroundColor
pStack.addArrangedSubview(v)
}
// constrain the stack view 20-pts from top, leading and trailing, 8-pts from bottom
NSLayoutConstraint.activate([
pStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
pStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
pStack.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
pStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
// no height constraint ...
// let the guageSection view height determine the stack view height
// guageSection has 10-pt tall view and multi-line capable label
])
}
override func layoutSubviews() {
super.layoutSubviews()
pStack.spacing = 2
}
}
class GuageTestViewController: UIViewController {
var gView = GuageView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 31.0 / 255.0, green: 46.0 / 255.0, blue: 61.0 / 255.0, alpha: 1.0)
view.addSubview(gView)
gView.translatesAutoresizingMaskIntoConstraints = false
// constrain the view to leading and trailing, and 40-pts from the top
NSLayoutConstraint.activate([
gView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0),
gView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0.0),
gView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
// no height constraint ...
// let the GuageView's content determine the height
])
}
}
在这篇文章之后,我试图在 StackViews 上按比例调整大小。
https://spin.atomicobject.com/2017/02/07/uistackviev-proportional-custom-uiviews/
假设是通过覆盖 intrinsicContentSize 我们可以指定一个新数字,它将计算出子视图大小的比例并相应地调整视图大小。
当我重复实施时,出现了一些奇怪的行为。比率保留,但最后一个项目被拉伸以占据剩余的 space,而不是项目在父视图的整个宽度上缩放(见下图)。
代码:
class GuageSection: UIView {
var width: Double = 1.0
override var intrinsicContentSize: CGSize {
return CGSize(width: width, height: 1.0)
}
}
这样使用
var guageWrapper = UIStackView()
guageWrapper.distribution = .fillProportionally
let guageSection = GuageSection()
guageSection.width = category.range // Currently Doubles ranging between 1.0 and 1.5
guageWrapper.addArrangedSubview(guageSection)
我试过使用 translateAutoResizingMaskInConstraints 属性 和其他一些东西,但似乎没有什么可以改变这种行为。
如果有人在正确方向上的一个好点之前看到过这种行为,我们将不胜感激。
我不知道这是否是 "bug",但是...看来 UIStackView
在 .fillProportionally
及其初始布局计算方面存在问题。
如果 .spacing
是 0
(零),.fillProportionally
似乎按记录工作。如果 .spacing
是 非零 ,我们会看到问题。
所以,试试这个...用 0
的间距初始化堆栈视图,然后:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guageWrapper.spacing = 2
}
当然,您需要引用 guageWrapper
,因此请将其创建为 class 级别的变量。
编辑:
我用堆栈视图创建了一个示例作为自定义 UIView
的一部分。
使用 1.0, 2.0, 1.0, 1.0, 1.5
的 "intrinsic widths" 数组,结果如下:
一切都是通过代码完成的(不需要 @IBOutlets
),因此您应该能够 运行 通过添加新的视图控制器并将其自定义 class 设置为 GuageTestViewController
:
//
// GuageTestViewController.swift
//
// Created by Don Mag on 2/28/19.
//
import UIKit
class GuageSection: UIView {
let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.numberOfLines = 0
v.font = UIFont.systemFont(ofSize: 14.0)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let colorView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
var width: Double = 1.0
override var intrinsicContentSize: CGSize {
return CGSize(width: width, height: 1.0)
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
self.addSubview(colorView)
self.addSubview(label)
NSLayoutConstraint.activate([
colorView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
colorView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
colorView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0.0),
colorView.heightAnchor.constraint(equalToConstant: 10.0),
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
label.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0.0),
label.widthAnchor.constraint(equalTo: colorView.widthAnchor, constant: 0.0),
label.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 2.0),
])
}
}
class GuageView: UIView {
var pStack = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
self.backgroundColor = UIColor(red: 41.0 / 255.0, green: 59.0 / 255.0, blue: 78.0 / 255.0, alpha: 1.0)
pStack.translatesAutoresizingMaskIntoConstraints = false
pStack.axis = .horizontal
pStack.alignment = .fill
pStack.distribution = .fillProportionally
pStack.spacing = 0
addSubview(pStack)
let labels = [
"Low", "Ideal", "Pre-High", "High", "Very High"
]
let rgbVals = [
[252, 191, 127],
[ 79, 197, 140],
[252, 191, 127],
[249, 129, 131],
[217, 92, 98],
]
let widths = [
1.0, 2.0, 1.0, 1.0, 1.5
]
for i in 0..<labels.count {
let v = GuageSection()
v.translatesAutoresizingMaskIntoConstraints = false
v.label.text = labels[i]
v.width = widths[i]
let rgb = rgbVals[i].compactMap { CGFloat([=11=]) / 255.0 }
v.colorView.backgroundColor = UIColor(red: rgb[0], green: rgb[1], blue: rgb[2], alpha: 1.0)
v.label.textColor = v.colorView.backgroundColor
pStack.addArrangedSubview(v)
}
// constrain the stack view 20-pts from top, leading and trailing, 8-pts from bottom
NSLayoutConstraint.activate([
pStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
pStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
pStack.topAnchor.constraint(equalTo: topAnchor, constant: 20.0),
pStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
// no height constraint ...
// let the guageSection view height determine the stack view height
// guageSection has 10-pt tall view and multi-line capable label
])
}
override func layoutSubviews() {
super.layoutSubviews()
pStack.spacing = 2
}
}
class GuageTestViewController: UIViewController {
var gView = GuageView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 31.0 / 255.0, green: 46.0 / 255.0, blue: 61.0 / 255.0, alpha: 1.0)
view.addSubview(gView)
gView.translatesAutoresizingMaskIntoConstraints = false
// constrain the view to leading and trailing, and 40-pts from the top
NSLayoutConstraint.activate([
gView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0.0),
gView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0.0),
gView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
// no height constraint ...
// let the GuageView's content determine the height
])
}
}