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
Making your app look good in both portrait and landscape layouts is something every developer needs to do on a daily basis. With the introduction of SwiftUI, it’s easier than ever to do so. However, since SwiftUI is still some years away from world domination, you may have to stick with your old friend Auto Layout a little longer.
In the beginning of the iPhone era, Apple made only one device: the original iPhone. With only one device, you only had to cater to one screen size.
But as years passed, Apple introduced more and more screen sizes as well as the iPad. Now, developers have to cater to a plethora of screen sizes. To make things easier, Apple introduced Auto Layout.
Auto What?
Auto Layout is a system of constraints, or UI-based rules, that govern the size and position of elements on the screen. That may sound simple on the surface but, as you’ll see in this tutorial, Auto Layout can get quite complex very quickly! With this in mind, you can use Auto Layout in two forms: via the Storyboard or programmatically.
I’m sure every developer has tried making an app via Storyboards. It’s simple, concise and has almost no learning curve.
But managing Storyboards can become very difficult if you have a big app. Additionally, a big app often means a big team. Collaborating with Storyboards is not an ideal experience if merge conflicts occur.
Implementing Auto Layout programmatically is a great solution to these problems. You can clearly see which constraints are applied. If merge conflicts occur they are in a Swift file, which you’re used to solving, and not in an alienated XML file that Storyboard generates behind the scenes.
Implementing Auto Layout by Code
There are different techniques you can use when implementing Auto Layout programmatically. The NSLayoutConstraint
class defines a relationship between two objects. The Auto Layout Visual Format Language, or VFL, allows you to define constraints by using an ASCII formatted string.
For this tutorial, you’ll use the NSLayoutAnchor
class. It has a very fluent API for creating constraints in code.
Gallery Example
In this tutorial, you’ll make a Gallery App using programmatic constraints. The app consists of a 2×2 grid of cards, each of which has the image of a character and its name. By harnessing the power of Auto Layout, you’ll ensure the app is consistent in both landscape and portrait layouts.
Getting Started
Use the Download Materials button at the top or bottom of this tutorial to download the Gallery begin project. Build and run. You’ll get a blank screen:
Since you’ll make this app with code, you’ll need to get rid of the Main.storyboard and make the necessary adjustments so your app gets an entry point.
Select Main.storyboard in the Project navigator and delete it from the project. Now, click on the project in the Project navigator, find the Main Interface option, and delete Main from there as well.
Now, go over to the Info.plist
, and expand the Application Scene Manifest entry. Keep on expanding the entries till you see the Storyboard Name entry and then delete it from there. Also delete the Main storyboard file base name entry from the plist.
You now have an app with no storyboard and no entry point. Not very useful. :]
Now you need to set up SceneDelegate.swift to create an entry point for the app. Open it and enter the following code in scene(_:willConnectTo session:options connectionOptions:)
:
guard let windowScene = scene as? UIWindowScene else { return }
//1
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
//2
window?.windowScene = windowScene
//3
window?.rootViewController = GalleryController()
//4
window?.makeKeyAndVisible()
Here’s what you did:
-
First, you set the global
window
variable to a newly createdUIWindow
with it’s frame as the bounds of thewindowScene
. -
Next you set the windowScene property of the
window
to the unwrappedwindowScene
property. -
Next, we assign the
rootViewController
of thewindow
to an instance of GalleryViewController. - Finally, you made the window the key window and also made it visible.
Build and run the app. Since you set the root view controller of the window to GalleryController it becomes the entry point of the app. You’ll see a blank screen again:
Adding Your First Constraints
To layout a view correctly, you have to set its constraints to something meaningful so Auto Layout can figure out its x-position, y-position, width and height. There are certain properties on every UIView
:
You have to set a combination of these constraints so Auto Layout can figure out the view’s position on the screen.
Open GalleryController.swift and have a quick look at the code already there. The GalleryController
already creates four instances of a CardView
— one for each of the cards you want to show. Notice that the translatesAutoresizingMaskIntoConstraints
property is set to false
because you want Auto Layout to dynamically calculate the views’ positions and sizes according to their constraints.
viewDidLoad()
then calls two pre-defined methods to add cards to the view and set up the card constraints. In setupViews()
only one card is currently being added to the view and setupConstraints()
— where you’ll create the constraints — is currently empty. It’s time to fix that.
Insert the following code in setupConstraints()
:
//1
NSLayoutConstraint.activate([
//2
cardView1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
cardView1.centerYAnchor.constraint(equalTo: view.centerYAnchor),
//3
cardView1.widthAnchor.constraint(equalToConstant: 120),
cardView1.heightAnchor.constraint(equalToConstant: 200)
])
Here’s what you did:
-
You added a call to
NSLayoutConstraint
to activate an array of constraints. The array contains: -
centerX
andcenterY
constraint anchors to make sure the card is laid out in the exact horizontal and vertical center of the view. -
width
andheight
constraint anchors to fix the width and height of the card.
Build and run the app and you’ll see the following screen:
Wait, what? The card is not what you expected.
That’s because you still have to add layout constraints to the subviews of the card, imageView
and textLabel
. Go to CardView.swift and insert the following code in setupViews()
:
addSubviews(imageView, textLabel)
This will add your subviews to the card view’s hierarchy, but these need constraints to position them correctly. Add the following code in setupConstraints()
:
//1
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: padding),
imageView.leadingAnchor.constraint(
equalTo: self.leadingAnchor,
constant: padding),
imageView.trailingAnchor.constraint(
equalTo: self.trailingAnchor,
constant: -padding)
])
NSLayoutConstraint.activate([
//2
textLabel.topAnchor.constraint(
equalTo: imageView.bottomAnchor,
constant: padding),
//3
textLabel.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
textLabel.trailingAnchor.constraint(
equalTo: self.trailingAnchor,
constant: -padding),
textLabel.bottomAnchor.constraint(
equalTo: self.bottomAnchor,
constant: -padding)
])
Here’s a breakdown of what you added:
-
You’ve given the
imageView
atopAnchor
,leadingAnchor
andtrailingAnchor
constraint. This tells theimageView
where its top, left and right sides should be positioned in relation to the parent view (referred to byself
here). -
Then you give
textLabel
atopAnchor
that is constrained relative to the bottom ofimageView
— wherever the bottom of the image is, the label will bepadding
points below it. -
You then add
leadingAnchor
,trailingAnchor
andbottomAnchor
constraints, telling thetextLabel
where its left, right and bottom sides should be positioned, relative to the parent view.
Build and run the app. You’ll see this:
Notice that the textLabel
is not the right size. That’s because you didn’t provide a constraint to determine the position of the bottom of the imageView
and it doesn’t know when to stop expanding or contracting. Time to fix that.