1

I am building a custom longitude/latitude selector. The view looks like:

textfield ° textfield ′ textfield ″ N|S

The components are all in a stack view. I would like the width of the textfields to auto-fit the content. Here is the code that I am using (the actual layout code is not a lot. Most of it is boilerplate):

class DMSLongLatInputView : UIView {
    private var degreeTextField: DMSLongLatTextField!
    private var minuteTextField: DMSLongLatTextField!
    private var secondTextField: DMSLongLatTextField!
    var signSelector: UISegmentedControl!
    let fontSize: CGFloat = 22
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        degreeTextField = DMSLongLatTextField()
        minuteTextField = DMSLongLatTextField()
        secondTextField = DMSLongLatTextField()
        signSelector = UISegmentedControl(items: ["N", "S"])
        signSelector.selectedSegmentIndex = 0
        signSelector.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: fontSize)], for: .normal)
        [degreeTextField, minuteTextField, secondTextField].forEach { (tf) in
            tf?.font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
        }
        let degreeLabel = UILabel()
        degreeLabel.text = "°"
        let minuteLabel = UILabel()
        minuteLabel.text = "′"
        let secondLabel = UILabel()
        secondLabel.text = "″"
        [degreeLabel, minuteLabel, secondLabel].forEach {
            l in l.font = UIFont.systemFont(ofSize: fontSize)
        }
        let stackView = UIStackView(arrangedSubviews:
            [degreeTextField,
             degreeLabel,
             minuteTextField,
             minuteLabel,
             secondTextField,
             secondLabel,
             signSelector
        ])
        stackView.arrangedSubviews.forEach { (v) in
            // I was hoping that this would make the widths of the stack view's subviews automatically
            // fit the content
            v.setContentCompressionResistancePriority(.required, for: .horizontal)
            v.setContentHuggingPriority(.required, for: .horizontal)
        }
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.distribution = .fill
        addSubview(stackView)
        stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        backgroundColor = .clear
    }
}

fileprivate class DMSLongLatTextField: UITextField, UITextFieldDelegate {
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        backgroundColor = .tertiarySystemFill
        placeholder = "00"
        borderStyle = .none
        textAlignment = .right
    }
}

This produces:

enter image description here

The first textfield got stretched by a lot, even though I set the content hugging priority to .required. I would like the first textfield to be only as wide as two digits, as that is how wide its placeholder is.

I suspected that this is because I used a wrong distribution, but I tried all the other 4 distributions, but none of them got the result I wanted. For example, .equalSpacing with spacing = 0 gave this result (as seen from the UI hierarchy inspector in the debugger):

enter image description here

Clearly the spacing is not 0! I would like all the subviews to be as close together as they can be, and stay centre-aligned. How can I do that?

Sweeper
  • 213,210
  • 22
  • 193
  • 313

1 Answers1

2

You're setting Leading and Trailing on the stack view... in other words, you're telling auto-layout to:

"Fill the width of the screen with the subviews."

Change your constraints to this:

    stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true

    //stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
    //stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    
    stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true

That should horizontally center your stack view and its subviews.

DonMag
  • 69,424
  • 5
  • 50
  • 86