Using auto layout to arrange views in sequence with one taking whatever space is available

101 views Asked by At

I'm trying to arrange 5 views horizontally and currently my visual format and view properties (colors omitted) look like this:

let questionLabel = UILabel()
questionLabel.text = "Did you take any additional medication?"
questionLabel.lineBreakMode = .ByWordWrapping
questionLabel.numberOfLines = 0

let questionSwitch = UISwitch()

let noLabel = UILabel()
noLabel.text = "No"
let yesLabel = UILabel()
yesLabel.text = "Yes"

for v in [questionLabel, questionSwitch, noLabel, yesLabel] {
    scrollView.addSubview(v)
    v.setTranslatesAutoresizingMaskIntoConstraints(false)
}
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|-20-[label]-(>=10,==10@900)-[no(==25)]-5-[switch]-5-[yes(==30)]-10-|",
        options: .AlignAllCenterY, metrics: nil,
        views: ["label":questionLabel, "switch":questionSwitch, "no":noLabel, "yes":yesLabel]))

I've hardcoded the width of the "No" and "Yes" labels because it would sometimes try to make them larger which would cause the appearance of larger margins. The only margin that I'd like to have a flexible size is between the question and the "No" label.

This works well when the width of the screen is a large enough to fit the text of the label on one line, but shifts things in an undesirable way when it has to do line breaks:

Good one Bad one

It does split the label into 2 lines, but then squishes "Yes" on top of the switch. There are no warnings about broken constraints in the debugger. What am I missing?

1

There are 1 answers

2
Good Doug On BEST ANSWER

Oftentimes, when I run into situations like this, I find it is most useful to put related items into their own views. In this case, I think you can put the switch and its labels into their own view. That way it can handle its own layout separately. I tried this in an example project and it worked for me:

let switchContainerView = UIView()
for v in [noLabel, questionSwitch, yesLabel] {
    switchContainerView.addSubview(v)
    v.setTranslatesAutoresizingMaskIntoConstraints(false)
}

let viewBindings = ["label":questionLabel, "switch":questionSwitch, "no":noLabel, "yes":yesLabel, "switchContainerView":switchContainerView]
switchContainerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[no(==25)]-5-[switch]-5-[yes(==30)]|", options: .AlignAllCenterY, metrics: nil, views: viewBindings))

for v in [questionLabel, switchContainerView] {
    view.addSubview(v)
    v.setTranslatesAutoresizingMaskIntoConstraints(false)
}
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-20-[label]-(>=10,==10@900)-[switchContainerView]-10-|", options: .AlignAllCenterY, metrics: nil, views: viewBindings))

What I did was create switchContainerView and add the switch and its labels. I did the layout for the switch on that container separately.