Day 30 - 100 Days of Swift

4 minute read

Project 6 (part 1)

Day 30 is the first part of the sixth project, another “technique” project. The technique for today is Auto Layout. First, you look at how to use constraints to make your view usable in both portrait and landscape orientations. Then you look at the Visual Format Language and use that to add constraints to some labels.

First, in a copy of the flag game project, you add some constraints that will keep all the flags on screen even if the device is rotated. You add a constraint from the bottom flag to the bottom of the view, to keep it at least 20 points above it. You add constraints to keep all the flags equal heights. And you add constraints to keep them at a constant aspect ratio:

Screenshot of constraints displayed in the storyboard

After adding those constraints it looks like this:

Screenshot of working app in vertical orientation.
Screenshot of working app in horizontal orientation.

Then you add a new app just to play around with the visual format language. First, you make a bunch of labels and add them to the view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// In viewDidLoad
let label1 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label1.backgroundColor = .red
label1.text = "THESE"
label1.sizeToFit()

let label2 = UILabel()
label2.translatesAutoresizingMaskIntoConstraints = false
label2.backgroundColor = .cyan
label2.text = "ARE"
label2.sizeToFit()

let label3 = UILabel()
label3.translatesAutoresizingMaskIntoConstraints = false
label3.backgroundColor = .yellow
label3.text = "SOME"
label3.sizeToFit()

let label4 = UILabel()
label4.translatesAutoresizingMaskIntoConstraints = false
label4.backgroundColor = .green
label4.text = "AWESOME"
label4.sizeToFit()

let label5 = UILabel()
label5.translatesAutoresizingMaskIntoConstraints = false
label5.backgroundColor = .orange
label5.text = "LABELS"
label5.sizeToFit()

view.addSubview(label1)
view.addSubview(label2)
view.addSubview(label3)
view.addSubview(label4)
view.addSubview(label5)

You give each of them a different color and text, so they are easy to distinguish, and you turn off translatesAutoresizingMaskIntoConstraints because we want to explicitly set the constraints. Then you add them to the view.

Then you build a dictionary to hold all your views:

1
2
3
4
5
6
7
8
// Still in viewDidLoad
let viewsDictionary = [
    "label1" : label1,
    "label2" : label2,
    "label3" : label3,
    "label4" : label4,
    "label5" : label5
]

Then comes the actually interesting part. You add the horizontal constraints to each of the labels in a for loop using this fancy visual format language:

1
2
3
4
5
6
7
8
9
// Set horizontal constraints
for label in viewsDictionary.keys {
    let constraints =
        NSLayoutConstraint.constraints(withVisualFormat: "H:|[\(label)]|",
                                       options: [],
                                       metrics: nil,
                                       views: viewsDictionary)
    view.addConstraints(constraints)
}

We’re using string interpolation to make sure each of the names of our labels is inserted throughout the loop. The formatting string H:|[label1]|" basically says “Constrain the leading edge of the label1 to the leading edge of its superview, and constrain the trailing edge of label1 to the trailing edge of its superview.” It uses the viewsDictionary that we pass it to lookup the views.

You set the vertical constraints in pretty much the same way, except you don’t need to loop, because they are all tied together:

1
2
3
4
5
6
7
// Set vertical constraints
let format = "V:|-36-[label1]-[label2]-[label3]-[label4]-[label5]"
let constraints = NSLayoutConstraint.constraints(withVisualFormat: format,
                                                 options: [],
                                                 metrics: nil,
                                                 views: viewsDictionary)
view.addConstraints(constraints)

I added 36 points of space to the top of the first label because I couldn’t figure out how to constrain it to the safe area with this language. Maybe it is something we’ll look at tomorrow. At the end of the day, it looks like this:

Screenshot of working app.

You can find my version of this project at the end of day 30 on Github here.

Reflections

Today was very short. I have a feeling the technique projects will always be shorter than the others, but this felt real short. It only took me 15 minutes to get through it. I think the visual format thing is kind of cool, but it seems like it would be really limited and hard to do complex layouts with. Also, kind of hard to maintain as screen sizes change and whatnot. Especially now that SwiftUI exists, where you can do something similar in a much safer, more terse and descriptive way (at least from what I’ve seen so far, I don’t actually have any experience with it yet).