SwiftGen with Image & Color Asset Catalogs

You might remember back in 2015 when iOS 9 was introduced, and we were finally given a way to manage all of our assets in one place with Asset Catalogs. A few years later, support for colors was added. However, to reference these assets, UIKit still requires us to reference their names as strings like so:

UIImage(named: "blog-asset")

Images, fonts, and colors all have to be referenced in this way. This has a lot of downsides given that we have to look up the asset names in order to reference them, it’s prone to typos, and there’s no auto-complete. Worst of all, if someone deletes the asset, the code still compiles.

Fortunately, there’s a great tool for this called SwiftGen which solves all of these problems. The image reference above simply becomes


We can also set this up to work with localized strings, fonts, colors, and more. However, you’ll notice when setting up SwiftGen that it’s not all that clear how to make it work with multiple different Asset Catalogs which is common to have in projects to store colors separately from images or even separate images by feature. In this post, we’ll go into the details of how we can set this up. SwiftGen provides a lot of flexibility for installing it into your project. We’ll touch on just one option, but I encourage you to look at the readme file on their GitHub page to see what installation is best for you.

Installing SwiftGen

For my project, I decided to go with the Homebrew installation method described in the SwiftGen readme file. Each installation method will have its pros and cons depending on how your project is setup. In my case, we’re also using Xcodegen which makes it easy to generate an Xcode project from a simple to use configuration file. Since we’ve installed SwiftGen via Homebrew, we can run it directly from a run script phase in our Xcode project’s “Build Phases” tab.

If you’re using Xcodegen, you can simply add a preGenCommand to the options: in your project.yml file to run SwiftGen.

  preGenCommand: swiftgen

Now our project is set up to run SwiftGen whenever the app builds. However, it won’t do much because we haven’t set up a configuration file that SwiftGen can use to find our assets and generate type-safe Swift code.

To get started with a configuration file, you can run the following command to generate a sample file:

swiftgen config init

Once you have this swiftgen.yml file, you’ll want to update it to conform to your needs. Here’s what my file looks like. I’ll step through what all of the pieces mean.

input_dir: ProjectName/Sources/Resources
output_dir: ProjectName/Sources/Generated/
  inputs: Localization.strings
    - templateName: structured-swift5
      output: Strings.swift
        enumName: Strings
  - inputs: Colors.xcassets
      - templateName: swift5
        output: Colors.swift
          enumName: Colors
  - inputs: Assets.xcassets
      - templatePath: swift5
        output: Assets-Constants.swift
          enumName: Assets

At the top there’s an input_dir and output_dir. These let us specify the directories we’d like SwiftGen to look for our assets (input_dir) and the directory the generated Swift code should be placed (output_dir). We’ll skip the details of our localized string setup here since it’s very similar to our assets setup we’ll describe below. For our assets property (xcassets:), we provide a file name for our input catalogs that are found in our input_dir. We have a Colors.xcassets catalog and an images catalog named Assets.xcassets. For our outputs, we define a few parameters (there are many more parameters to customize to your project in the documentation). The templateName tells SwiftGen how to generate the asset names into Swift code. Here we’re using the globally available swift5. We also output to a file named Colors.swift and Assets-Constants.swift which goes into our output_dir. Finally, we use params:enumName: to provide a name to reference our colors and images with. In this case, we’ll reference our colors as Colors.someColorName.color and images as Assets.someImageName.image. If our enumName: for our images had been Images instead, we’d access the images as Images.someImageName.image instead.

Now that we have our config file in place to support two different asset catalogs and a localized string file, we should be able to generate some swift code. However, if you were to run this now using swift5 for the templatePath: on both xcassets: outputs and you’re on a version of Swiftgen older than 6.2.1, you’d receive build errors with multiple duplicate definitions. This is because SwiftGen will generate some of the same boilerplate code to support referencing the assets for both catalogs. Since we only need to generate this boilerplate once, we’ll need to define a custom template for one of our asset catalogs that strip out this redundant boilerplate code and only provides new enum references for our assets (if you’re on the latest version of Swiftgen, you can ignore the custom template steps). It sounds complicated, but it’s not too bad. We simply create a new template file we’ll call custom-assets-template.stencil and copy paste the following into it:

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

{% if catalogs %}
{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %}
{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %}
{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %}
{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %}
{% set colorAlias %}{{param.colorAliasName|default:"AssetColorTypeAlias"}}{% endset %}
{% set imageAlias %}{{param.imageAliasName|default:"AssetImageTypeAlias"}}{% endset %}
{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %}
{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %}
#if os(OSX)
  import AppKit.NSImage
#elseif os(iOS) || os(tvOS) || os(watchOS)
  import UIKit.UIImage

// swiftlint:disable superfluous_disable_command
// swiftlint:disable file_length

// MARK: - Asset Catalogs

{% macro enumBlock assets %}
  {% call casesBlock assets %}
  {% if param.allValues %}

  // swiftlint:disable trailing_comma
  {{accessModifier}} static let allColors: [{{colorType}}] = [
    {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %}
  {{accessModifier}} static let allDataAssets: [{{dataType}}] = [
    {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %}
  {{accessModifier}} static let allImages: [{{imageType}}] = [
    {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %}
  // swiftlint:enable trailing_comma
  {% endif %}
{% endmacro %}
{% macro casesBlock assets %}
  {% for asset in assets %}
  {% if asset.type == "color" %}
  {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}")
  {% elif asset.type == "data" %}
  {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}")
  {% elif asset.type == "image" %}
  {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}")
  {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
  {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {

    {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %}
  {% elif asset.items %}
  {% call casesBlock asset.items %}
  {% endif %}
  {% endfor %}
{% endmacro %}
{% macro allValuesBlock assets filter prefix %}
  {% for asset in assets %}
  {% if asset.type == filter %}
  {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %}
  {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %}
  {% call allValuesBlock asset.items filter prefix2 %}
  {% elif asset.items %}
  {% call allValuesBlock asset.items filter prefix %}
  {% endif %}
  {% endfor %}
{% endmacro %}
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
{{accessModifier}} enum {{enumName}} {
  {% if catalogs.count > 1 %}
  {% for catalog in catalogs %}
  {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} {
    {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %}
  {% endfor %}
  {% else %}
  {% call enumBlock catalogs.first.assets %}
  {% endif %}
// swiftlint:enable identifier_name line_length nesting type_body_length type_name

private final class BundleToken {}
{% else %}
// No assets found
{% endif %}

Save this in your project directory. I put mine in a /Config directory at the root of my project. Next, update the templateName: reference in the swiftgen.yml file to point to this new template file like so:

- inputs: Assets.xcassets
      - templatePath: Config/custom-assets-template.stencil
        output: Assets-Constants.swift
          enumName: Assets

Easy! Now, when SwiftGen runs at compile time, the build errors should go away and we should be able to reference our strings, colors, and images without string references! The best part is, the colors even work with dark mode and all of the features that come with using asset catalogs without the aforementioned downsides.

SwiftGen is super powerful and highly customizable. I definitely encourage digging into their extensive documentation and playing around with it yourself to make it work perfect for your project setup.

About the Author

Object Partners profile.
Leave a Reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]