Swift 带有嵌套堆栈视图的 UIStackView 间距
Swift UIStackView spacing with nested stack view(s)
按照 Apple 指南 here(我以编程方式完成),我创建了 3 个嵌套在垂直堆栈视图中的水平堆栈视图。为了使文本字段对齐,它们每个也在水平堆栈视图中。
有没有办法快速浏览文本字段堆栈视图,以便我可以看到 3 个字符的占位符文本?我也尝试了 .fill
、.fillProportionally
和其他方法,但水平堆栈视图最终以 50/50 的标签
结束
我是自动布局的新手,所以我将其构建为一个学习练习。必须有更好的方法来做到这一点,对吧?
类似这样(标签为 1/4-1/3 space,文本字段为 2/3-3/4)。
import UIKit
class RunningPaceCalculatorViewController: UIViewController {
private lazy var timeDistancePaceStackView: UIStackView = {
let timeStackView = UIStackView.textFieldsStackView("Time")
let distanceStackView = UIStackView.distanceStackView()
let paceStackView = UIStackView.textFieldsStackView("Pace")
return UIStackView.customStackView(.vertical, backgroundColor: .systemTeal, subviews: [timeStackView, distanceStackView, paceStackView])
}()
private lazy var buttonStackView: UIStackView = {
return UIStackView.buttonStackView()
}()
private lazy var constainerStackView: UIStackView = {
return UIStackView.customStackView(.vertical, backgroundColor: .gray, subviews: [timeDistancePaceStackView, buttonStackView])
}()
override func viewDidLoad() {
setUpView()
}
func setUpView() {
view.backgroundColor = .orange
view.addSubview(constainerStackView)
NSLayoutConstraint.activate([
constainerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.55),
constainerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constainerStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
constainerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
constainerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
}
private extension UITextField {
static func customTextField(_ placeholder: String) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.backgroundColor = .white
return textField
}
}
private extension UIStackView {
static func customStackView(_ orientation: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution = UIStackView.Distribution.fill, backgroundColor: UIColor, subviews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subviews)
stackView.axis = orientation
stackView.distribution = .fillEqually
stackView.backgroundColor = backgroundColor
stackView.spacing = 5.0
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
static func textFieldsStackView(_ title: String) -> UIStackView {
let COLON = ":"
let label = UILabel.customLabel(title)
label.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
label.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
let paceTextField1 = UITextField.customTextField("Hrs")
let colonLabel1 = UILabel.customLabel(COLON)
let paceTextField2 = UITextField.customTextField("Min")
let colonLabel2 = UILabel.customLabel(COLON)
let paceTextField3 = UITextField.customTextField("Sec")
let textFieldContainerStackView = UIStackView.customStackView(.horizontal, backgroundColor: .systemPink, subviews: [paceTextField1, colonLabel1, paceTextField2, colonLabel2, paceTextField3])
textFieldContainerStackView.setContentHuggingPriority(.defaultLow - 50, for: .horizontal)
textFieldContainerStackView.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
return UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemBlue, subviews: [label, textFieldContainerStackView])
}
static func distanceStackView() -> UIStackView {
let DISTANCE = "Distance"
let label = UILabel.customLabel(DISTANCE)
let distanceTextField = UITextField.customTextField(DISTANCE)
return UIStackView.customStackView(.horizontal, distribution: .equalCentering, backgroundColor: .systemGray, subviews: [label, distanceTextField])
}
static func buttonStackView() -> UIStackView {
let calculateButton = UIButton.customButton("Calculate")
let resetButton = UIButton.customButton("Reset")
return UIStackView.customStackView(.horizontal, backgroundColor: .green, subviews: [calculateButton, resetButton])
}
}
private extension UIButton {
static func customButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
return button
}
}
private extension UILabel {
static func customLabel(_ text: String) -> UILabel {
let timeLabel = UILabel()
timeLabel.text = text
return timeLabel
}
}
您可以在标签和 textfieldsStackview 之间添加一个透明的 UIView(所谓的间隔视图)并为其添加宽度约束。容器堆栈视图(包含所有三个视图)的分布应该是 .fill
.
[UILabel][UIView][UIStackView]
[UILabel][UIView][UIStackView]
[UILabel][UIView][UIStackView]
import UIKit
class RunningPaceCalculatorViewController: UIViewController {
private lazy var timeDistancePaceStackView: UIStackView = {
let timeStackView = UIStackView.textFieldsStackView("Time")
let distanceStackView = UIStackView.distanceStackView()
let paceStackView = UIStackView.textFieldsStackView("Pace")
return UIStackView.customStackView(.vertical, backgroundColor: .systemTeal, subviews: [timeStackView, distanceStackView, paceStackView])
}()
private lazy var buttonStackView: UIStackView = {
return UIStackView.buttonStackView()
}()
private lazy var constainerStackView: UIStackView = {
return UIStackView.customStackView(.vertical, backgroundColor: .gray, subviews: [timeDistancePaceStackView, buttonStackView])
}()
override func viewDidLoad() {
setUpView()
}
func setUpView() {
view.backgroundColor = .orange
view.addSubview(constainerStackView)
NSLayoutConstraint.activate([
constainerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.55),
constainerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constainerStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
constainerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
constainerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
}
private extension UITextField {
static func customTextField(_ placeholder: String) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.backgroundColor = .white
return textField
}
}
private extension UIStackView {
static func customStackView(_ orientation: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution = UIStackView.Distribution.fill, backgroundColor: UIColor, subviews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subviews)
stackView.axis = orientation
stackView.distribution = .fillEqually
stackView.backgroundColor = backgroundColor
stackView.spacing = 5.0
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
static func textFieldsStackView(_ title: String) -> UIStackView {
let COLON = ":"
let label = UILabel.customLabel(title)
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
label.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
let spacerView = UIView()
spacerView.backgroundColor = .clear
spacerView.translatesAutoresizingMaskIntoConstraints = false
spacerView.widthAnchor.constraint(equalToConstant: 10).isActive = true
let paceTextField1 = UITextField.customTextField("Hrs")
paceTextField1.translatesAutoresizingMaskIntoConstraints = false
let colonLabel1 = UILabel.customLabel(COLON)
colonLabel1.translatesAutoresizingMaskIntoConstraints = false
colonLabel1.widthAnchor.constraint(equalToConstant: 20).isActive = true
let paceTextField2 = UITextField.customTextField("Min")
paceTextField2.translatesAutoresizingMaskIntoConstraints = false
let colonLabel2 = UILabel.customLabel(COLON)
colonLabel2.translatesAutoresizingMaskIntoConstraints = false
colonLabel2.widthAnchor.constraint(equalToConstant: 20).isActive = true
let paceTextField3 = UITextField.customTextField("Sec")
paceTextField3.translatesAutoresizingMaskIntoConstraints = false
let textFieldContainerStackView = UIStackView.customStackView(.horizontal, backgroundColor: .systemPink, subviews: [paceTextField1, colonLabel1, paceTextField2, colonLabel2, paceTextField3])
textFieldContainerStackView.setContentHuggingPriority(.defaultLow - 50, for: .horizontal)
textFieldContainerStackView.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
textFieldContainerStackView.distribution = .fill
paceTextField1.widthAnchor.constraint(equalTo: paceTextField2.widthAnchor).isActive = true
paceTextField2.widthAnchor.constraint(equalTo: paceTextField3.widthAnchor).isActive = true
let stackView = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemBlue, subviews: [label, spacerView, textFieldContainerStackView])
stackView.distribution = .fill
return stackView
}
static func distanceStackView() -> UIStackView {
let DISTANCE = "Distance"
let label = UILabel.customLabel(DISTANCE)
let distanceTextField = UITextField.customTextField(DISTANCE)
return UIStackView.customStackView(.horizontal, distribution: .equalCentering, backgroundColor: .systemGray, subviews: [label, distanceTextField])
}
static func buttonStackView() -> UIStackView {
let calculateButton = UIButton.customButton("Calculate")
let resetButton = UIButton.customButton("Reset")
return UIStackView.customStackView(.horizontal, backgroundColor: .green, subviews: [calculateButton, resetButton])
}
}
private extension UIButton {
static func customButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
return button
}
}
private extension UILabel {
static func customLabel(_ text: String) -> UILabel {
let timeLabel = UILabel()
timeLabel.text = text
return timeLabel
}
}
可能更容易有一个垂直堆栈视图,其中包含具有自动布局约束的常规 UIView,然后让每个 UIView 包含一个 UILabel 和另一个水平 UIStackView(或文本视图,用于第 2 行) .这样您就可以轻松控制标签和子视图的大小和间距。
创建“列”可能有点棘手...
根据你的图片和描述,我猜你想要这个(或至少接近这个)作为你的最终结果:
要做到这一点,请结合以下几点:
- 将水平“行”堆栈视图的分布设置为
.fill
- 将水平“字段:字段:字段”堆栈视图的分布设置为
.fill
- 使用
.multiplier
给每个“标题”标签一个 widthAnchor
等于其堆栈视图的百分比(给每个“行”相同的百分比值)
- 将“冒号”标签的 HUGGING 设置为
.required
- 将“min”和“sec”字段宽度限制设置为等于“hrs”字段宽度
这是您的代码,已修改以生成我的屏幕截图。我试图只更改必要的内容,我 认为 我包含了足够多的注释以使其清楚:
class RunningPaceCalculatorViewController: UIViewController {
// percent of overall width for Time/Distance/Pace "column" of labels
private let labelWidth: CGFloat = 0.33
private lazy var timeDistancePaceStackView: UIStackView = {
// added titleWidth parameter -- percent of width of stack view
let timeStackView = UIStackView.textFieldsStackView("Time", titleWidth: labelWidth)
let distanceStackView = UIStackView.distanceStackView(labelWidth)
let paceStackView = UIStackView.textFieldsStackView("Pace", titleWidth: labelWidth)
// include setting distribution parameter
return UIStackView.customStackView(.vertical, distribution: .fillEqually, backgroundColor: .systemTeal, subviews: [timeStackView, distanceStackView, paceStackView])
}()
private lazy var buttonStackView: UIStackView = {
return UIStackView.buttonStackView()
}()
private lazy var constainerStackView: UIStackView = {
// include setting distribution parameter
return UIStackView.customStackView(.vertical, distribution: .fillEqually, backgroundColor: .gray, subviews: [timeDistancePaceStackView, buttonStackView])
}()
override func viewDidLoad() {
setUpView()
}
func setUpView() {
view.backgroundColor = .orange
view.addSubview(constainerStackView)
NSLayoutConstraint.activate([
constainerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.55),
// we're setting Leading and Trailing, so we don't need centerX
//constainerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constainerStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
constainerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
constainerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
}
private extension UITextField {
static func customTextField(_ placeholder: String) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.backgroundColor = .white
return textField
}
}
private extension UIStackView {
static func customStackView(_ orientation: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution = UIStackView.Distribution.fill, backgroundColor: UIColor, subviews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subviews)
stackView.axis = orientation
// use distribution parameter, not hard-coded .fillEqually
//stackView.distribution = .fillEqually
stackView.distribution = distribution
stackView.backgroundColor = backgroundColor
stackView.spacing = 5.0
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
static func textFieldsStackView(_ title: String, titleWidth: CGFloat) -> UIStackView {
let COLON = ":"
let label = UILabel.customLabel(title)
let paceTextField1 = UITextField.customTextField("Hrs")
let colonLabel1 = UILabel.customLabel(COLON)
let paceTextField2 = UITextField.customTextField("Min")
let colonLabel2 = UILabel.customLabel(COLON)
let paceTextField3 = UITextField.customTextField("Sec")
// include setting distribution parameter
let textFieldContainerStackView = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemPink, subviews: [paceTextField1, colonLabel1, paceTextField2, colonLabel2, paceTextField3])
// create the stack view to return
let retStack = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemBlue, subviews: [label, textFieldContainerStackView])
// colon labels should HUG their content
colonLabel1.setContentHuggingPriority(.required, for: .horizontal)
colonLabel2.setContentHuggingPriority(.required, for: .horizontal)
NSLayoutConstraint.activate([
// constrain "title label" width to percentage of stack view width
label.widthAnchor.constraint(equalTo: retStack.widthAnchor, multiplier: titleWidth),
// constrain text field widths to each other
paceTextField2.widthAnchor.constraint(equalTo: paceTextField1.widthAnchor),
paceTextField3.widthAnchor.constraint(equalTo: paceTextField1.widthAnchor),
])
return retStack
}
static func distanceStackView(_ titleWidth: CGFloat) -> UIStackView {
let DISTANCE = "Distance"
let label = UILabel.customLabel(DISTANCE)
let distanceTextField = UITextField.customTextField(DISTANCE)
// create the stack view to return
let retStack = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemGray, subviews: [label, distanceTextField])
NSLayoutConstraint.activate([
// constrain "title label" width to percentage of stack view width
label.widthAnchor.constraint(equalTo: retStack.widthAnchor, multiplier: titleWidth),
])
return retStack
}
static func buttonStackView() -> UIStackView {
let calculateButton = UIButton.customButton("Calculate")
let resetButton = UIButton.customButton("Reset")
// include setting distribution parameter
return UIStackView.customStackView(.horizontal, distribution: .fillEqually, backgroundColor: .green, subviews: [calculateButton, resetButton])
}
}
private extension UIButton {
static func customButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
return button
}
}
private extension UILabel {
static func customLabel(_ text: String) -> UILabel {
let timeLabel = UILabel()
timeLabel.text = text
return timeLabel
}
}
按照 Apple 指南 here(我以编程方式完成),我创建了 3 个嵌套在垂直堆栈视图中的水平堆栈视图。为了使文本字段对齐,它们每个也在水平堆栈视图中。
有没有办法快速浏览文本字段堆栈视图,以便我可以看到 3 个字符的占位符文本?我也尝试了 .fill
、.fillProportionally
和其他方法,但水平堆栈视图最终以 50/50 的标签
我是自动布局的新手,所以我将其构建为一个学习练习。必须有更好的方法来做到这一点,对吧?
类似这样(标签为 1/4-1/3 space,文本字段为 2/3-3/4)。
import UIKit
class RunningPaceCalculatorViewController: UIViewController {
private lazy var timeDistancePaceStackView: UIStackView = {
let timeStackView = UIStackView.textFieldsStackView("Time")
let distanceStackView = UIStackView.distanceStackView()
let paceStackView = UIStackView.textFieldsStackView("Pace")
return UIStackView.customStackView(.vertical, backgroundColor: .systemTeal, subviews: [timeStackView, distanceStackView, paceStackView])
}()
private lazy var buttonStackView: UIStackView = {
return UIStackView.buttonStackView()
}()
private lazy var constainerStackView: UIStackView = {
return UIStackView.customStackView(.vertical, backgroundColor: .gray, subviews: [timeDistancePaceStackView, buttonStackView])
}()
override func viewDidLoad() {
setUpView()
}
func setUpView() {
view.backgroundColor = .orange
view.addSubview(constainerStackView)
NSLayoutConstraint.activate([
constainerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.55),
constainerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constainerStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
constainerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
constainerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
}
private extension UITextField {
static func customTextField(_ placeholder: String) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.backgroundColor = .white
return textField
}
}
private extension UIStackView {
static func customStackView(_ orientation: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution = UIStackView.Distribution.fill, backgroundColor: UIColor, subviews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subviews)
stackView.axis = orientation
stackView.distribution = .fillEqually
stackView.backgroundColor = backgroundColor
stackView.spacing = 5.0
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
static func textFieldsStackView(_ title: String) -> UIStackView {
let COLON = ":"
let label = UILabel.customLabel(title)
label.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
label.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
let paceTextField1 = UITextField.customTextField("Hrs")
let colonLabel1 = UILabel.customLabel(COLON)
let paceTextField2 = UITextField.customTextField("Min")
let colonLabel2 = UILabel.customLabel(COLON)
let paceTextField3 = UITextField.customTextField("Sec")
let textFieldContainerStackView = UIStackView.customStackView(.horizontal, backgroundColor: .systemPink, subviews: [paceTextField1, colonLabel1, paceTextField2, colonLabel2, paceTextField3])
textFieldContainerStackView.setContentHuggingPriority(.defaultLow - 50, for: .horizontal)
textFieldContainerStackView.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
return UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemBlue, subviews: [label, textFieldContainerStackView])
}
static func distanceStackView() -> UIStackView {
let DISTANCE = "Distance"
let label = UILabel.customLabel(DISTANCE)
let distanceTextField = UITextField.customTextField(DISTANCE)
return UIStackView.customStackView(.horizontal, distribution: .equalCentering, backgroundColor: .systemGray, subviews: [label, distanceTextField])
}
static func buttonStackView() -> UIStackView {
let calculateButton = UIButton.customButton("Calculate")
let resetButton = UIButton.customButton("Reset")
return UIStackView.customStackView(.horizontal, backgroundColor: .green, subviews: [calculateButton, resetButton])
}
}
private extension UIButton {
static func customButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
return button
}
}
private extension UILabel {
static func customLabel(_ text: String) -> UILabel {
let timeLabel = UILabel()
timeLabel.text = text
return timeLabel
}
}
您可以在标签和 textfieldsStackview 之间添加一个透明的 UIView(所谓的间隔视图)并为其添加宽度约束。容器堆栈视图(包含所有三个视图)的分布应该是 .fill
.
[UILabel][UIView][UIStackView]
[UILabel][UIView][UIStackView]
[UILabel][UIView][UIStackView]
import UIKit
class RunningPaceCalculatorViewController: UIViewController {
private lazy var timeDistancePaceStackView: UIStackView = {
let timeStackView = UIStackView.textFieldsStackView("Time")
let distanceStackView = UIStackView.distanceStackView()
let paceStackView = UIStackView.textFieldsStackView("Pace")
return UIStackView.customStackView(.vertical, backgroundColor: .systemTeal, subviews: [timeStackView, distanceStackView, paceStackView])
}()
private lazy var buttonStackView: UIStackView = {
return UIStackView.buttonStackView()
}()
private lazy var constainerStackView: UIStackView = {
return UIStackView.customStackView(.vertical, backgroundColor: .gray, subviews: [timeDistancePaceStackView, buttonStackView])
}()
override func viewDidLoad() {
setUpView()
}
func setUpView() {
view.backgroundColor = .orange
view.addSubview(constainerStackView)
NSLayoutConstraint.activate([
constainerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.55),
constainerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constainerStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
constainerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
constainerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
}
private extension UITextField {
static func customTextField(_ placeholder: String) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.backgroundColor = .white
return textField
}
}
private extension UIStackView {
static func customStackView(_ orientation: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution = UIStackView.Distribution.fill, backgroundColor: UIColor, subviews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subviews)
stackView.axis = orientation
stackView.distribution = .fillEqually
stackView.backgroundColor = backgroundColor
stackView.spacing = 5.0
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
static func textFieldsStackView(_ title: String) -> UIStackView {
let COLON = ":"
let label = UILabel.customLabel(title)
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
label.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
let spacerView = UIView()
spacerView.backgroundColor = .clear
spacerView.translatesAutoresizingMaskIntoConstraints = false
spacerView.widthAnchor.constraint(equalToConstant: 10).isActive = true
let paceTextField1 = UITextField.customTextField("Hrs")
paceTextField1.translatesAutoresizingMaskIntoConstraints = false
let colonLabel1 = UILabel.customLabel(COLON)
colonLabel1.translatesAutoresizingMaskIntoConstraints = false
colonLabel1.widthAnchor.constraint(equalToConstant: 20).isActive = true
let paceTextField2 = UITextField.customTextField("Min")
paceTextField2.translatesAutoresizingMaskIntoConstraints = false
let colonLabel2 = UILabel.customLabel(COLON)
colonLabel2.translatesAutoresizingMaskIntoConstraints = false
colonLabel2.widthAnchor.constraint(equalToConstant: 20).isActive = true
let paceTextField3 = UITextField.customTextField("Sec")
paceTextField3.translatesAutoresizingMaskIntoConstraints = false
let textFieldContainerStackView = UIStackView.customStackView(.horizontal, backgroundColor: .systemPink, subviews: [paceTextField1, colonLabel1, paceTextField2, colonLabel2, paceTextField3])
textFieldContainerStackView.setContentHuggingPriority(.defaultLow - 50, for: .horizontal)
textFieldContainerStackView.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
textFieldContainerStackView.distribution = .fill
paceTextField1.widthAnchor.constraint(equalTo: paceTextField2.widthAnchor).isActive = true
paceTextField2.widthAnchor.constraint(equalTo: paceTextField3.widthAnchor).isActive = true
let stackView = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemBlue, subviews: [label, spacerView, textFieldContainerStackView])
stackView.distribution = .fill
return stackView
}
static func distanceStackView() -> UIStackView {
let DISTANCE = "Distance"
let label = UILabel.customLabel(DISTANCE)
let distanceTextField = UITextField.customTextField(DISTANCE)
return UIStackView.customStackView(.horizontal, distribution: .equalCentering, backgroundColor: .systemGray, subviews: [label, distanceTextField])
}
static func buttonStackView() -> UIStackView {
let calculateButton = UIButton.customButton("Calculate")
let resetButton = UIButton.customButton("Reset")
return UIStackView.customStackView(.horizontal, backgroundColor: .green, subviews: [calculateButton, resetButton])
}
}
private extension UIButton {
static func customButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
return button
}
}
private extension UILabel {
static func customLabel(_ text: String) -> UILabel {
let timeLabel = UILabel()
timeLabel.text = text
return timeLabel
}
}
可能更容易有一个垂直堆栈视图,其中包含具有自动布局约束的常规 UIView,然后让每个 UIView 包含一个 UILabel 和另一个水平 UIStackView(或文本视图,用于第 2 行) .这样您就可以轻松控制标签和子视图的大小和间距。
创建“列”可能有点棘手...
根据你的图片和描述,我猜你想要这个(或至少接近这个)作为你的最终结果:
要做到这一点,请结合以下几点:
- 将水平“行”堆栈视图的分布设置为
.fill
- 将水平“字段:字段:字段”堆栈视图的分布设置为
.fill
- 使用
.multiplier
给每个“标题”标签一个widthAnchor
等于其堆栈视图的百分比(给每个“行”相同的百分比值) - 将“冒号”标签的 HUGGING 设置为
.required
- 将“min”和“sec”字段宽度限制设置为等于“hrs”字段宽度
这是您的代码,已修改以生成我的屏幕截图。我试图只更改必要的内容,我 认为 我包含了足够多的注释以使其清楚:
class RunningPaceCalculatorViewController: UIViewController {
// percent of overall width for Time/Distance/Pace "column" of labels
private let labelWidth: CGFloat = 0.33
private lazy var timeDistancePaceStackView: UIStackView = {
// added titleWidth parameter -- percent of width of stack view
let timeStackView = UIStackView.textFieldsStackView("Time", titleWidth: labelWidth)
let distanceStackView = UIStackView.distanceStackView(labelWidth)
let paceStackView = UIStackView.textFieldsStackView("Pace", titleWidth: labelWidth)
// include setting distribution parameter
return UIStackView.customStackView(.vertical, distribution: .fillEqually, backgroundColor: .systemTeal, subviews: [timeStackView, distanceStackView, paceStackView])
}()
private lazy var buttonStackView: UIStackView = {
return UIStackView.buttonStackView()
}()
private lazy var constainerStackView: UIStackView = {
// include setting distribution parameter
return UIStackView.customStackView(.vertical, distribution: .fillEqually, backgroundColor: .gray, subviews: [timeDistancePaceStackView, buttonStackView])
}()
override func viewDidLoad() {
setUpView()
}
func setUpView() {
view.backgroundColor = .orange
view.addSubview(constainerStackView)
NSLayoutConstraint.activate([
constainerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.55),
// we're setting Leading and Trailing, so we don't need centerX
//constainerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constainerStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
constainerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
constainerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
])
}
}
private extension UITextField {
static func customTextField(_ placeholder: String) -> UITextField {
let textField = UITextField()
textField.placeholder = placeholder
textField.backgroundColor = .white
return textField
}
}
private extension UIStackView {
static func customStackView(_ orientation: NSLayoutConstraint.Axis, distribution: UIStackView.Distribution = UIStackView.Distribution.fill, backgroundColor: UIColor, subviews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subviews)
stackView.axis = orientation
// use distribution parameter, not hard-coded .fillEqually
//stackView.distribution = .fillEqually
stackView.distribution = distribution
stackView.backgroundColor = backgroundColor
stackView.spacing = 5.0
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
static func textFieldsStackView(_ title: String, titleWidth: CGFloat) -> UIStackView {
let COLON = ":"
let label = UILabel.customLabel(title)
let paceTextField1 = UITextField.customTextField("Hrs")
let colonLabel1 = UILabel.customLabel(COLON)
let paceTextField2 = UITextField.customTextField("Min")
let colonLabel2 = UILabel.customLabel(COLON)
let paceTextField3 = UITextField.customTextField("Sec")
// include setting distribution parameter
let textFieldContainerStackView = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemPink, subviews: [paceTextField1, colonLabel1, paceTextField2, colonLabel2, paceTextField3])
// create the stack view to return
let retStack = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemBlue, subviews: [label, textFieldContainerStackView])
// colon labels should HUG their content
colonLabel1.setContentHuggingPriority(.required, for: .horizontal)
colonLabel2.setContentHuggingPriority(.required, for: .horizontal)
NSLayoutConstraint.activate([
// constrain "title label" width to percentage of stack view width
label.widthAnchor.constraint(equalTo: retStack.widthAnchor, multiplier: titleWidth),
// constrain text field widths to each other
paceTextField2.widthAnchor.constraint(equalTo: paceTextField1.widthAnchor),
paceTextField3.widthAnchor.constraint(equalTo: paceTextField1.widthAnchor),
])
return retStack
}
static func distanceStackView(_ titleWidth: CGFloat) -> UIStackView {
let DISTANCE = "Distance"
let label = UILabel.customLabel(DISTANCE)
let distanceTextField = UITextField.customTextField(DISTANCE)
// create the stack view to return
let retStack = UIStackView.customStackView(.horizontal, distribution: .fill, backgroundColor: .systemGray, subviews: [label, distanceTextField])
NSLayoutConstraint.activate([
// constrain "title label" width to percentage of stack view width
label.widthAnchor.constraint(equalTo: retStack.widthAnchor, multiplier: titleWidth),
])
return retStack
}
static func buttonStackView() -> UIStackView {
let calculateButton = UIButton.customButton("Calculate")
let resetButton = UIButton.customButton("Reset")
// include setting distribution parameter
return UIStackView.customStackView(.horizontal, distribution: .fillEqually, backgroundColor: .green, subviews: [calculateButton, resetButton])
}
}
private extension UIButton {
static func customButton(_ title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
return button
}
}
private extension UILabel {
static func customLabel(_ text: String) -> UILabel {
let timeLabel = UILabel()
timeLabel.text = text
return timeLabel
}
}