首页 > 解决方案 > 带有嵌套堆栈视图的 Swift UIStackView 间距

问题描述

Swift UIStackView 的图像

按照此处的 Apple 指南(我已经以编程方式完成),我创建了 3 个嵌套在垂直堆栈视图中的水平堆栈视图。为了使文本字段对齐,它们也都在水平堆栈视图中。

有没有办法跳过文本字段堆栈视图,以便我可以看到 3 个字符的占位符文本?我也尝试过.fill,.fillProportionally和其他方法,但水平堆栈视图最终以 50/50 和标签结束

我是自动布局的新手,所以我将其构建为学习练习。必须有更好的方法来做到这一点,对吧?

像这样的东西(标签空间为 1/4-1/3,文本字段为 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
}
}

标签: iosswiftautolayoutuikituistackview

解决方案


您可以在标签和 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
    }
}

在此处输入图像描述


推荐阅读