SwiftGen Tutorial for iOS

Learn how SwiftGen makes it easy to get rid of magic strings in your iOS projects. By Andy Pereira.

5 (3) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Creating Custom Templates

Up to this point, your swiftgen.yml file has used default templates provided by SwiftGen. All these are stored in the SwiftGen pod. If these templates don’t quite provide the functionality you want, you can create your own templates to generate code how you would like. Templates are built using Stencil, an open-source project that provides a templating language for Swift. In this section, you’ll learn how to modify existing templates and use them to generate your code.

If you look in the Project navigator, you see there’s a folder named Templates. In that, there are two subfolders: fonts and xcassets. With these, you’ll have SwiftGen provide support to use colors and fonts directly in SwiftUI.

Supporting SwiftUI Colors

To add SwiftUI Color support, open assets_swift5_swiftui.stencil. Things might be a bit overwhelming at first, because the file doesn’t have any code syntax support.

On line 13, add the following line of code:

import SwiftUI

Next, go back to swiftgen.yml. In your first entry, for xcassets, find the line where you define the template name:

templateName: swift5

Now, replace it with the following:

templatePath: Templates/xcassets/assets_swift5_swiftui.stencil

Here, you’ve changed from using templateName to templatePath. This tells SwiftGen to use your custom template rather than a built-in one.

Build the project, then go to XCAssets+Generated.swift. At the top, you’ll now see:

import SwiftUI

Because you added the import to the Stencil file, when the code was generated, it picked up this change and added the new import. Pretty cool, right?

Open assets_swift5_swiftui.stencil and replace:

// Add Support For SwiftUI Here

With the following:

{{accessModifier}} private(set) lazy var color: Color = {
  Color(systemColor)
}()

In this code, you get to see how to use Stencil a bit more. Here’s what you added:

  • {{accessModifier}}: This is how you tell Stencil how to replace with something provided during the code generation. If you look at line 11 of this file, you see accessModifier defined as a variable. By default, the access modifier is internal. If you remember from before, you saw how you can change this to public
  • The rest of this is actually just standard code. It creates a SwiftUI color from a UIKit color.
Note: There was one other modification made to the template as part of the starter materials. It changes the type Color to SystemColor.

Build the project again, then go back to XCAssets+Generated.swift. On line 62, you should see the code from your template, now generated as real code.

Now, you need to swap out any hard-coded reference to a color to use your new functionality. Open DrinksListView.swift and find where the foreground color is set on the navigation title text. Replace it with the following:

Text(Strings.DrinkList.Navigation.title)
  .font(Font.custom("NotoSans-Bold", size: 17, relativeTo: .body))
  .foregroundColor(Asset.Colors.textColor.color)

Here, you’ve switched away from using a hard-coded string, with true SwiftUI support to reference a color directly.

You can use colors directly in UIKit, as well. Open AppMain.swift. Change the following line of code:

appearance.backgroundColor = UIColor(named: "header")

To be this:

appearance.backgroundColor = Asset.Colors.header.systemColor

Here, systemColor is a reference to the platform specific color type. It’ll be UIColor if you’re on iOS, and NSColor if you’re on macOS.

Two files have colors declared using strings:

  • AppMain.swift
  • DrinksListView.swift

Finish converting the rest of the colors from using strings in each of these files following the example above.

Supporting SwiftUI Fonts

Just as SwiftUI’s Color isn’t currently supported in SwiftGen, neither is Font. Start by opening fonts_swift5_swiftui.stencil and adding the following import to the top of the file:

import SwiftUI

Next, replace this block of code found near the end of the file:

// Add Support For SwiftUI here
fileprivate extension Font {
}

With the following:

fileprivate extension Font {
  // 1
  static func mappedFont(_ name: String, textStyle: TextStyle) -> Font {
    let fontStyle = mapToUIFontTextStyle(textStyle)
    let fontSize = UIFont.preferredFont(forTextStyle: fontStyle).pointSize
    return Font.custom(name, size: fontSize, relativeTo: textStyle)
  }

  // 2
  static func mapToUIFontTextStyle(
    _ textStyle: SwiftUI.Font.TextStyle
  ) -> UIFont.TextStyle {
    switch textStyle {
    case .largeTitle:
      return .largeTitle
    case .title:
      return .title1
    case .title2:
      return .title2
    case .title3:
      return .title3
    case .headline:
      return .headline
    case .subheadline:
      return .subheadline
    case .callout:
      return .callout
    case .body:
      return .body
    case .caption:
      return .caption1
    case .caption2:
      return .caption2
    case .footnote:
      return .footnote
    @unknown default:
      fatalError("Missing a TextStyle mapping")
    }
  }
}

Here’s what you added:

  1. mappedFont(_:textStyle:) creates a custom font from a name and TextStyle. The style is used to get the standard, default font size each font should be.
  2. mapToUIFontTextStyle(_:) simply provides a 1:1 mapping of a SwiftUI TextStyle to a UIKit TextStyle

Next, find this comment in the middle of the file:

// Add Support For SwiftUI Here

Replace that with the following:

{{accessModifier}} func textStyle(_ textStyle: Font.TextStyle) -> Font {
  Font.mappedFont(name, textStyle: textStyle)
}

This block of code is similar to what you added to provide Color support. The only difference here is that it’s specific to providing fonts directly in SwiftUI.

Now, open swiftgen.yml and add the following entry:

## Fonts
fonts:
  inputs:
    - Resources/Noto_Sans
  outputs:
    templatePath: Templates/fonts/fonts_swift5_swiftui.stencil
    output: Fonts+Generated.swift

This app uses two fonts, both located in the group Resources:

  1. NotoSans
  2. NotoSans-Bold

This new entry just needs to know where the parent folder of the font you’d like to support is. Everything else is similar to all the other entries you’ve added before.

Build your app and add Fonts+Generated.swift to Generated. Once you do, open Fonts+Generated.swift. Here, you can see how the font family gets organized. You should see that NotoSans has the following variations available:

  • Regular
  • Bold
  • BoldItalic
  • Italic

Like all the other generated code in the app, it’s fairly easy to use. Open AppMain.swift and replace the first line of application(_:didFinishLaunchingWithOptions:) with the following:

let buttonFont = FontFamily.NotoSans.bold.font(size: 16)

Here, you directly set the font size to the bold NotoSans font.

Next, go to DrinksListView.swift, find the first reference to a font, in the NavigationLink, and replace it with the following:

Text(drinkStore.drinks[index].name)
  .font(FontFamily.NotoSans.bold.textStyle(.body))

Here, you take advantage of your customized template’s code that can generate a custom SwiftUI font, size to match a default TextStyle: in this case body.

Finally, finish converting all the usages of font throughout the app. In both DrinksListView.swift and DrinkDetailView.swift, you’ll find several places where font is set. Following the example above, you can convert the code away from using a string to the appropriate weight of NotoSans. Each of these placements already indicates which TextStyle they should have.

Build and run. Your app should still look identical to how it did when you started. But you should now have all resources referenced in a type-safe way!

DrinksUp Initial Launch