SwiftGen - Strongly typed assets in Xcode

I remember when I was in university, learning Android SDK and writing my first mobile app. All assets were bundled together in nifty R class. With this autogenerated assets class, developers could use strongly typed names, and get autocompletion for image names, string names, etc.

We don't have such a thing in Xcode out of the box. Every developer has to make choice: use strings as names or reach for a third-party solution. In my case, SwiftGen comes to help.

In this tutorial, I will show you how to use basic templates and how to implement your own .stencil files for automated code generation of colors and Storyboard. We will start with a base project containing one storyboard and separate assets catalog with colors. Our goal is to automatically create swift files with our assets.

I will use the downloaded binary of SwiftGen because this will allow me to simplify integration with the project.

Built-in features

To start, we have to create a swiftgen.yml file containing our configuration. It will tell SwiftGen which files to parse, what templates to use, and where to save the output:

input_dir: GenerateMe
output_dir: GenerateMe/Generated/
xcassets:
  - inputs:
      - Colors.xcassets
    outputs:
      - templateName: swift5
        output: Colors.swift
ib:
  - inputs:
    - Base.lproj/Main.storyboard
    outputs:
      templateName: scenes-swift5
      output: Storyboard.swift

Configuration complete! At the top we specify input and output folders, then the configuration for generating colors, and at the end configuration for our storyboard.

To make everything work properly, create a Generated folder inside GenerateMe. To run the first code generation and create new files, in the root project folder run swiftgen/bin/swiftgen (it can fail for the first time due to gatekeeper on Mac, go to privacy settings and allow execution). Inside the Generated folder, you will see two new files. Add them in Xcode!

Open Storyboard.swift. It has an initial view controller but details are missing! SwiftGen uses Storyboard ID to generate and instantiate view controllers from storyboards. Open Main.storyboard and name details view controller however you like. Save and run the generation script again. Now StoryboardScene.Main enum has a new case.

Let's look at Colors.swift, for me, it is too complicated for our simple use. Maybe we should write our own template ( .stencil file) with simpler output code? Yes!

Custom templates

In the root folder create Templates folder and inside colors.stencil file and open it with your favorite text editor.

// swiftlint:disable all
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen

import UIKit.UIColor

typealias Color = UIColor

// MARK: - Colors

extension Color {
  {% for color in catalogs.first.assets %}
    static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Color(named: "{{ color.name }}")!
  {% endfor %}
}

// swiftlint:enable all

Our new stencil code is very simple. According to documentation xcassets structure holds an array of catalogs that holds an array of assets. We know that this stencil file will be used for only one catalog, and only one type of asset - colors.

If you have ever done some web frontend work, creating custom templates should feel familiar. When code is generated, SwiftGen uses context to generate our code.

Inside the template we can use if-else statements, loops, create variables, and define macros (functions), all necessary info can be found in stencil docs.

I decided to simplify generating colors by adding an extension to the UIColor. Inside the extension, the template will be populated with new static let fields of every color inside the assets catalog.

Now, all we have to do is save the template file and make a small change to the configuration. We have to change the template to our own custom implementation:

input_dir: GenerateMe
output_dir: GenerateMe/Generated/
xcassets:
  - inputs:
      - Colors.xcassets
    outputs:
      - templatePath: Templates/colors.stencil
        output: Colors.swift
ib:
  - inputs:
    - Base.lproj/Main.storyboard
    outputs:
      templateName: scenes-swift5
      output: Storyboard.swift

Automation

The best results are guaranteed by automation. We will make Xcode generate assets during every build. To do that add a new Run Script phase in project settings. Move it before Compile sources step. I will name my new script SwiftGen. The script itself is very simple:

$SRCROOT/swiftgen/bin/swiftgen

Now, with everything in place, Xcode will always regenerate files if assets changes.

You can find the full solution here.