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
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

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 for textLabel to a value slightly higher than the default (which is what the imageView is still using). Similarly, you set the contentCompressionResistance 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 and cardView4 have similar leading and trailing constraints to cardView1 and cardView2, but have a bottom constraint instead of a top constraint. They also have a top constraint which aligns to the bottom of cardView1 and cardView2 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.

Note: This last iteration of 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.