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.