【问题标题】:Swift UIStackView spacing with nested stack view(s)带有嵌套堆栈视图的 Swift UIStackView 间距
【发布时间】:2021-04-27 06:50:01
【问题描述】:

按照 Apple 的指南 here(我已改为以编程方式完成),我创建了 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
}
}

【问题讨论】:

    标签: ios swift autolayout uikit uistackview


    【解决方案1】:

    您可以在标签和 textfieldsStackview 之间添加一个透明的 UIView(所谓的间隔视图)并为其添加宽度约束。容器stackview(包含所有三个视图)的分布应该是.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
        }
    }
    

    【讨论】:

    • 您能举个例子吗?当我将分布设置为.fill 并添加一个间隔视图并添加一个像spacerView.widthAnchor.constraint(equalToConstant: 1) or 1,000,000 这样的约束时,它会在标签和文本字段之间产生更大的间隙
    【解决方案2】:

    拥有一个包含具有 AutoLayout 约束的常规 UIViews 的垂直堆栈视图可能更容易,然后让每个 UIViews 包含一个 UILabel 和另一个水平 UIStackView(或文本视图,用于第 2 行)。这样您就可以轻松控制标签和子视图的大小和间距。

    【讨论】:

    • 在界面构建器中做所有事情变得更加容易?
    【解决方案3】:

    创建“列”可能有点棘手......

    根据你的图片和描述,我猜你想要这个(或至少接近这个)作为你的最终结果:

    要做到这一点,需要做几件事:

    • 将水平“行”堆栈视图上的分布设置为.fill
    • 将水平“字段:字段:字段”堆栈视图上的分布设置为.fill
    • 使用.multiplier为每个“标题”标签赋予一个等于其堆栈视图百分比的widthAnchor(赋予每个“行”相同的百分比值)
    • 将“冒号”标签的 HUGGING 设置为 .required
    • 将“min”和“sec”字段宽度限制设置为等于“hrs”字段宽度

    这是您的代码,经过修改以生成我的屏幕截图。我尝试只更改必要的内容,并且我认为我包含了足够多的 cmets 以使其清楚:

    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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-10
      • 1970-01-01
      相关资源
      最近更新 更多