SnapKit for iOS: Constraints in a Snap

In this tutorial you’ll learn about SnapKit, a lightweight DSL (domain-specific language) to make Auto Layout and constraints a breeze to work with. By Shai Mishali.

Leave a rating/review
Download materials
Save for later
Share

In this tutorial you’ll learn about SnapKit, a lightweight DSL (domain-specific language) to make Auto Layout and constraints a breeze to work with. You’ll come away from this tutorial being able to write your layout code like a layout ninja would!

Auto Layout is a powerful tool to describe the relationships and constraints between different views and complex view hierarchies in your application, but writing these constraints can often be quite non-intuitive at first.

Up until a few years ago, writing these constraints programmatically was quite tedious with cryptic and verbose methods such as using Visual Formatting Language or manually creating NSLayoutConstraints.

iOS 9 greatly improved these mechanisms with the introduction of Layout Anchors, which make creating constraints quite intuitive and declarative. And yet, there is still much to be desired to make it even snappier for you to create constraints. This is exactly where SnapKit comes into play!

Getting Started

Throughout this tutorial, you’ll work on SnappyQuiz — a simple game where the player gets random questions/statements and picks whether they’re true or false.

Use the Download Materials button at the top or bottom of this tutorial to download the starter project for this tutorial. Open SnappyQuiz.xcodeproj and wait for Xcode to fetch the SnapKit dependency.

The project comprises a few Swift files you’ll need:

  • QuizViewController.swift: This is where the layout of the screen happens, including defining the views.
  • QuizViewController+Logic.swift: This file houses the logic of the game itself. You won’t need to change this file in this tutorial.
  • QuizViewController+Constraints.swift: All of the constraints of the screen’s UI are located in in this file, which is where you’ll do most of the work.

The project also includes State.swift which represents the game state and Questions.swift where the raw question data is found, but you won’t really touch these during this tutorial.

Build and run the project. You should see the first question with a countdown timer ticking as well as a progress bar representing the current game progress:

SnappyQuiz running

In QuizViewController+Constraints.swift, explore setupConstraints(). This code uses the aforementioned Layout Anchors to define the relations between the different views in the app. You can find more about Layout Anchors in our Easier Auto Layout tutorial.

In this tutorial, you’ll replace each of these constraints with its SnapKit variation.

Snappin’ & Chainin’

Before you actually modify the SnappyQuiz application, it’s time for you to learn a bit more about what SnapKit actually is. In the introduction to this tutorial, I mentioned SnapKit uses a DSL, but what does that actually mean?

What is a DSL?

A domain-specific language (DSL) is a language created to express and deal with a specific domain or to solve a specific problem.

In SnapKit’s case, it aims to create a syntax that is much more intuitive and easy-to-use specifically for Auto Layout constraints.

An important thing to understand is that, as a DSL, SnapKit is mostly syntactic sugar — you can do anything SnapKit does without SnapKit. However, SnapKit provides a much more fluent and expressive syntax to solve this specific domain and problem.

SnapKit Basics

Take a very common set of constraints — attaching a view to all of its superview’s edges:

Without SnapKit, the code would look similar to the following:

child.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
  child.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
  child.topAnchor.constraint(equalTo: parent.topAnchor),
  child.trailingAnchor.constraint(equalTo: parent.trailingAnchor),
  child.bottomAnchor.constraint(equalTo: parent.bottomAnchor),
])

This is quite declarative, but SnapKit can do better.

SnapKit introduces a namespace called snp on every UIView (and NSView, on macOS) in your system. That namespace, along with the makeConstraints(_:) method, are the essence of SnapKit.

SnapKit represents those constraints like this:

child.snp.makeConstraints { make in
  make.leading.equalToSuperview()
  make.top.equalToSuperview()
  make.trailing.equalToSuperview()
  make.bottom.equalToSuperview()
}

This might seem like a similar amount of code, but it greatly improves readability. Two things you might’ve noticed are:

  1. You don’t need to reference parent at all, thanks to SnapKit’s equalToSuperview(). This means that, even if child moves to a different parent view, you won’t need to modify this code.
  2. The make syntax create an almost-English-like syntax, e.g. “make leading equal to superview“, which is much nicer to read.

Composability & Chaining

You just saw your first SnapKit code, but where SnapKit really shines is its composition capabilities. You can chain any anchors together, as well as the constraints themselves.

You can rewrite the example above as:

child.snp.makeConstraints { make in
  make.leading.top.trailing.bottom.equalToSuperview()
}

Or even more concisely as:

child.snp.makeConstraints { make in
  make.edges.equalToSuperview()
}

Want to add an inset of 16 to your view? Another simple chaining will get you there:

child.snp.makeConstraints { make in
  make.edges.equalToSuperview().inset(16)
}

As you can see, composability and chaining are at the core of SnapKit and provide expressiveness you simply can’t achieve with vanilla NSLayoutConstraints.

Your First Constraints

Now that you have some of the basics of SnapKit, it’s time for you to convert all of the constraints in setupConstraints() to use it. It’s much simpler than you’d expect, and you’ll go through these one-by-one, exploring SnapKit’s various capabilities.

Go back to QuizViewController+Constraints.swift and find setupConstraints(). You’ll start modifying constraints below the updateProgress(to: 0) line. You’ll go back to the constraints above that line later on.

Find the following block of code, defining constraints for the timer label:

lblTimer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
  lblTimer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.45),
  lblTimer.heightAnchor.constraint(equalToConstant: 45),
  lblTimer.topAnchor.constraint(equalTo: viewProgress.bottomAnchor, constant: 32),
  lblTimer.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])

Replace it with the following:

lblTimer.snp.makeConstraints { make in
  make.width.equalToSuperview().multipliedBy(0.45) // 1
  make.height.equalTo(45) // 2
  make.top.equalTo(viewProgress.snp.bottom).offset(32) // 3
  make.centerX.equalToSuperview() // 4
}

Like before, this is a direct translation of the original constraints, using SnapKit’s chaining syntax. Quickly breaking this down:

  1. Make the label’s width equal to the superview’s width, multiplied by 0.45 (i.e., 45% of the superview’s width).
  2. Set the label’s height to a static 45.
  3. Constrain the top of the label to the bottom of the progress bar, offset by 32.
  4. Center the X axis to the superview’s X axis, making the label horizontally centered.

While not too different from the NSLayoutConstraint-based code, it provides much better readability and scoping of the views being constrained.

Note: Notice something else different? SnapKit no longer requires you to set translatesAutoresizingMaskIntoConstraints to false! The library does it for you. No more forgetting to do that and tirelessly debugging messed up constraints :].