Search code examples
iosswiftswift3autolayoutnslayoutconstraint

Autolayout using anchors not laying out my views the way I intended?


So I have a fairly simple cell view that I am trying to customize. I have a bunch of fields and label that I would like to layout as shown in the below image

enter image description here

However what I end up with using the constraints I have setup looks something like this...

indexLabelBackground (circle)

indexLabel (number)

repsField (0 the left of REPS)

repsLabel (REPS string)

dividerLabel ("/" string)

weightField (0 to the left of POUNDS)

weightLabel (POUNDS string)

enter image description here

I have looked at my constraints over and over again and I can't find anything that would lead me to think my constraints are wrong so Im hoping a fresh pair of eyes can help me identify this puzzling problem...

P.S. I even created a new project and attempted to recreate this layout using storyboards using the same constraints which seemed to look good.

private func setupViews() {
    addSubview(indexLabelBackground)
    indexLabelBackground.addSubview(indexLabel)
    addSubview(repsField)
    addSubview(repsLabel)
    addSubview(dividerLabel)
    addSubview(weightField)
    addSubview(weightLabel)

    indexLabelBackground.leftAnchor.constraint(equalTo: leftAnchor, constant: 24).isActive = true
    indexLabelBackground.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    indexLabelBackground.heightAnchor.constraint(equalToConstant: 24).isActive = true
    indexLabelBackground.widthAnchor.constraint(equalToConstant: 24).isActive = true

    indexLabel.centerXAnchor.constraint(equalTo: indexLabelBackground.centerXAnchor).isActive = true
    indexLabel.centerYAnchor.constraint(equalTo: indexLabelBackground.centerYAnchor).isActive = true

    repsField.leftAnchor.constraint(equalTo: indexLabelBackground.rightAnchor, constant: 8).isActive = true
    repsField.rightAnchor.constraint(equalTo: repsLabel.leftAnchor, constant: -8).isActive = true
    repsField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true

    repsLabel.rightAnchor.constraint(equalTo: dividerLabel.leftAnchor, constant: -16).isActive = true
    repsLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true

    dividerLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
    dividerLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true

    weightField.leftAnchor.constraint(equalTo: dividerLabel.rightAnchor, constant: 16).isActive = true
    weightField.rightAnchor.constraint(equalTo: weightLabel.leftAnchor, constant: -8).isActive = true
    weightField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true

    weightLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -16).isActive = true
    weightLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
//  weightLabel.widthAnchor.constraint(equalToConstant: 60).isActive = true //note this fixes the problem but forces my label to be a fixed width
    }

So something to note is that if you look at the last line, this can be "fixed" in a hacky way such as adding a width constraint to the weightLabel. However I'd like to avoid this as it seems hacky and unnecessary with the right constraints.


Solution

  • The problem is that you have multiple elements (fields and labels) in your cell that can be stretched to make the layout work, and you haven't told Auto Layout which ones not to stretch.

    You want your labels to stick to their intrinsic content size (be as big as they need to be to show the text, but no bigger). You specify this by setting a high content hugging priority. By default, when you programmatically create UITextFields and UILabels, their content hugging priority is low (250). Since they are all low, Auto Layout will stretch them in some unknown order to make the layout work.

    I suggest adding these 3 lines to set the content hugging priority to high (750) for your 3 labels:

    repsLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal)
    dividerLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal)
    weightLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal)
    

    That way, your labels will always be the size needed to hold their text, and they can then anchor your layout (pun intended).

    I even created a new project and attempted to recreate this layout using storyboards using the same constraints which seemed to look good.

    In the Storyboard, UILabels have a higher default content hugging priority (251) than UITextFields (250), so it just works. It beats me why they chose to give them all low (250) when created programmatically.