Building an App With Only Code Using Auto Layout
Learn how to make your iOS app’s UI in code using Auto Layout without using Storyboards or XIBs, and how it can make working in a team easier. By Bhagat Singh.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Building an App With Only Code Using Auto Layout
15 mins
Layout Priorities
You must be wondering why the label is shrinking, when you have all the constraints that you want? Actually AutoLayout at this point does not know how much to scale the textLabel
or the imageView
because neither of them has a fixed height. This results in a Content Priority Ambiguity Error.
For different screen sizes, you’re going to have different amounts of space to distribute between the two elements. So, how should Auto Layout distribute that extra space? Does it give it to the imageView
or the textLabel
? Or do they both get equal amounts?
If you don’t solve this problem yourself, then Auto Layout will try to do it for you, and the results are unpredictable.
The solution here is to set the label’s Content Hugging priority. Here, Hugging means something like size to fit. A higher value of Content Hugging will make the label stick to its original bounds and not expand.
You’ll also have to set the Compression Resistance priority of the label. Here, Compression Resistance means the label will resist compressing. A higher value of Compression Resistance will prevent the label from compressing or shrinking with respect to other views.
Add these lines in setupConstraints()
:
textLabel.setContentHuggingPriority(.defaultLow + 1, for: .vertical)
textLabel
.setContentCompressionResistancePriority(.defaultHigh + 1, for: .vertical)
With this:
-
You set the
contentHuggingPriority
fortextLabel
to a value slightly higher than the default (which is what theimageView
is still using). Similarly, you set thecontentCompressionResistance
to a value slightly higher than the default. The scale used for both is arbitrary, going between 0 and 1000.
Build and run the app. You’ll see the textLabel
take the correct size now:
Laying Out the Rest of the Cards
Now that you’ve completely laid out the first CardView, hop back to GalleryController.swift and layout all of the other cards.
First, replace the code in setupViews()
with the following:
view.addSubviews(cardView1, cardView2, cardView3, cardView4)
This adds the rest of the subviews to the view’s hierarchy.
Next, replace the code in setupConstraints()
with this:
let safeArea = view.safeAreaLayoutGuide
let viewFrame = view.bounds
//card 1
NSLayoutConstraint.activate([
cardView1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cardView1.topAnchor.constraint(equalTo: safeArea.topAnchor),
cardView1.widthAnchor.constraint(equalToConstant: viewFrame.width/2),
cardView1.heightAnchor.constraint(equalToConstant: viewFrame.height/2)
])
//card 2
NSLayoutConstraint.activate([
cardView2.leadingAnchor.constraint(equalTo: cardView1.trailingAnchor),
cardView2.topAnchor.constraint(equalTo: safeArea.topAnchor),
cardView2.widthAnchor.constraint(equalToConstant: viewFrame.width/2),
cardView2.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
cardView2.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
//card 3
NSLayoutConstraint.activate([
cardView3.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cardView3.topAnchor.constraint(equalTo: cardView1.bottomAnchor),
cardView3.widthAnchor.constraint(equalToConstant: viewFrame.width/2),
cardView3.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
cardView3.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor)
])
//card 4
NSLayoutConstraint.activate([
cardView4.leadingAnchor.constraint(equalTo: cardView3.trailingAnchor),
cardView4.topAnchor.constraint(equalTo: cardView2.bottomAnchor),
cardView4.widthAnchor.constraint(equalToConstant: viewFrame.width/2),
cardView4.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
cardView4.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor)
])
Here’s a breakdown:
-
cardView1
has a leading constraint, so it fixes itself with the leading edge of the view. Similarly,cardView2
has a trailing constraint so it fixes itself with the trailing edge of the view. Both have a top constraint so they align themselves to the top of the view. -
cardView3
andcardView4
have similar leading and trailing constraints tocardView1
andcardView2
, but have a bottom constraint instead of a top constraint. They also have a top constraint which aligns to the bottom ofcardView1
andcardView2
respectively. - All of these views have a width and height constraint equal to half the view’s width and height respectively.
-
You also constrain the subviews to the view’s
safeAreaLayoutGuide
‘s top and bottom constraints to automatically take care of devices with notches, such as the iPhone X and iPhone 11.
Build and run the app. You’ll see the cards layout exactly as you need them. :]
But as soon as you turn your simulator into landscape mode, it doesn’t look right. You can turn your simulator to landscape mode pressing Command-right arrow or by selecting Simulator ▸ Hardware ▸ Rotate Right.
This is because you haven’t accounted for the change in the view’s width and height when the phone isn’t in the portrait orientation. It’s a good idea to avoid making constraints to sizes that could change, like the width or height of another view.
To fix this, you need to add constraints that take care of themselves even if the view’s orientation changes. You’ll do this by using the multiplier
property of each constraint to allow Auto Layout to calculate the actual values each time it makes a layout pass.
Also note that we add all constraints to the view’s safeAreaLayoutGuide
, so that it takes care of the notch devices on it’s own.
Replace the code in setupConstraints()
once more with the following:
let safeArea = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
cardView1.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
cardView1.topAnchor.constraint(equalTo: safeArea.topAnchor),
cardView1.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.5),
cardView1.heightAnchor.constraint(equalTo: safeArea.heightAnchor,
multiplier: 0.5),
cardView2.leadingAnchor.constraint(equalTo: cardView1.trailingAnchor),
cardView2.topAnchor.constraint(equalTo: safeArea.topAnchor),
cardView2.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.5),
cardView2.heightAnchor.constraint(equalTo: safeArea.heightAnchor,
multiplier: 0.5),
cardView2.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
cardView3.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
cardView3.topAnchor.constraint(equalTo: cardView1.bottomAnchor),
cardView3.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.5),
cardView3.heightAnchor.constraint(equalTo: safeArea.heightAnchor,
multiplier: 0.5),
cardView3.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor),
cardView4.leadingAnchor.constraint(equalTo: cardView3.trailingAnchor),
cardView4.topAnchor.constraint(equalTo: cardView2.bottomAnchor),
cardView4.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.5),
cardView4.heightAnchor.constraint(equalTo: safeArea.heightAnchor,
multiplier: 0.5),
cardView4.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor),
])
For each card, you have made its width and height to be half of the available width and height.
setupConstraints()
illustrates another best practice when setting up Auto Layout in code. Each call to NSLayoutConstraint.activate(_:)
results in a layout pass. As a result, Apple recommends activating all constraints in a single call, as you see above.Build and run the app once again to get the desired result:
Sweet! You’ve got your cards laid out in both landscape and portrait orientations and you did it completely in code!
Where to Go From Here?
In this tutorial, you learned on how to layout views programmatically, without using Storyboards. It can seem a little bit time consuming and daunting at first. But once you get the hang of it then it’s really a breeze, especially with large teams.
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.
If you want to learn more about Auto Layout using Storyboards, check here.
You can also check out SnapKit, an Auto Layout wrapper for iOS, here. It abstracts all boilerplate code and provides a declarative API to make constraints by code. However, you can also write your own wrapper to abstract out the redundant code.
If you have any questions or comments, please don’t hesitate to join the forum discussion below.