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.

Leave a rating/review
Download materials
Save for later
Share

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:

  1. First, you set the global window variable to a newly created UIWindow with it’s frame as the bounds of the windowScene.
  2. Next you set the windowScene property of the window to the unwrapped windowScene property.
  3. Next, we assign the rootViewController of the window to an instance of GalleryViewController.
  4. 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.

Note: The leading and trailing anchors represent a left-to-right reading locale in the above diagram. For a right-to-left locale, the leading and trailing constraints are swapped automatically.

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:

  1. You added a call to NSLayoutConstraint to activate an array of constraints. The array contains:
  2. centerX and centerY constraint anchors to make sure the card is laid out in the exact horizontal and vertical center of the view.
  3. width and height 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:

  1. You’ve given the imageView a topAnchor, leadingAnchor and trailingAnchor constraint. This tells the imageView where its top, left and right sides should be positioned in relation to the parent view (referred to by self here).
  2. Then you give textLabel a topAnchor that is constrained relative to the bottom of imageView — wherever the bottom of the image is, the label will be padding points below it.
  3. You then add leadingAnchor, trailingAnchor and bottomAnchor constraints, telling the textLabel where its left, right and bottom sides should be positioned, relative to the parent view.
Note: You used negative padding for bottom and trailing constraints. This is because the padding is measured from the parent’s trailing and bottom edges respectively, so it has to be negative in the x or y direction respectively too.

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.