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 2 of 4 of this article. Click here to view the first page.

Using Additional Basic Templates

There are a few additional templates you can take advantage of, without any need for customization.

Working With Interface Builder

The app you’re working with is using SwiftUI. However, to showcase SwiftGen’s ability to work with Interface Builder, the sample project includes a storyboard and some view controllers. Start by generating code to support Interface Builder or Storyboards by adding the following to swiftgen.yml:

## Interface Builder
ib:
  inputs:
    # 1
    - .
  outputs:
    # 2
    - templateName: scenes-swift5
      output: IB-Scenes+Generated.swift
    # 3
    - templateName: segues-swift5
      output: IB-Segues+Generated.swift

Here, you’ve done the following:

  1. Indicated to SwiftGen that you want it to look for any interface builder supported file in your project’s root directory.
  2. One great thing about SwiftGen is that it separates the concept of Scenes from Segues. This indicates the file your scenes will output to.
  3. Finally, this indicates where all segue information should be output to.

Build your project. Add IB-Scenes+Generate.swift and IB-Segues+Generated.swift to the Generated group, like you did for XCAssets+Generated.swift.

Now, you can replace the scene or segue information in the app.

Start by opening InformationViewController.swift. In InformationViewController, replace the implementation of showAbout() with the following:

performSegue(
  withIdentifier: StoryboardSegue.Main.showAbout.rawValue, 
  sender: self)

All segues will generate as actual enum cases, which makes things easier when you’re checking which segue triggered. For the sake of simplicity, the code you added is just triggering the segue.

Next, inside InformationView, replace makeUIViewController(context:) with the following:

func makeUIViewController(context: Context) -> some UIViewController {
  StoryboardScene.Main.initialScene.instantiate()
}

Here, you’ve simplified the code required to instantiate the initial view controller of a storyboard. SwiftGen provides a helper method to quickly access your initial scene.

Build and run. Tap Learn More at the top right. You’ll see a modal appear, showing you some information about the app. This is calling into makeUIViewController(context:) to know which view to load.

Show Learn More screen

Now, tap the About button. You’ll trigger the segue you modified.

DrinksUp! About screen

Working With JSON

The last quick win you’ll add is support for JSON. Add the following to the end of swiftgen.yml:

## JSON
json:
  inputs:
    - Resources/
  outputs:
    templateName: runtime-swift5
    output: JSON+Generated.swift

This now provides a way to reference any JSON files you have in the project’s resources. You’ll use this to convert the mock data bundled with the app.

Build the project, then add JSON+Generated.swift to your Generated group.

Now, open Drink.swift. Completely remove the struct named MockData. Then, replace the extension for DrinkStore, found at the bottom, with the following:

extension DrinkStore {
  static var mockData: [Drink] {
    do {
      let data = try JSONSerialization.data(
        withJSONObject: JSONFiles.starterDrinks,
        options: [])
      let mockDrinks = try JSONDecoder().decode([Drink].self, from: data)
      return mockDrinks
    } catch {
      print(error.localizedDescription)
      return []
    }
  }
}

Here, you should notice your code references JSONFiles.starterDrinks. Open MockData.json and notice the first key in the file, named starterDrinks. SwiftGen has taken this top-level object and provided it as a static property on JSONFiles for you to reference as needed.

Build and run. You shouldn’t notice anything different from before — just the drinks showing up in the list.

DrinksUp Initial Launch

Working With Strings

Perhaps one of the biggest conveniences SwiftGen provides is the ability to use variables to reference localized strings. It’s a great practice to place any text you’ll present to your app users inside of localized strings or stringsdict files. But if you’ve done this, you know that once you get beyond a handful of strings, it becomes difficult to remember what strings are available. It also feels redundant that you have a string in your strings file and … a string in your code.

This project contains the following strings files:

  • Localizable.strings: Your run-of-the-mill strings file, made of keys and values.
  • Localizable.stringsdict: You should use stringsdict files whenever you need to worry about pluralized strings. This file type supports not only translating strings but also how to pluralize words for any of the variations a language requires.

To convert all your strings files, add the following to swiftgen.yml:

## Strings
strings:
  inputs:
    # 1
    - en.lproj
  outputs:
    - templateName: structured-swift5
      # 2
      params:
        publicAccess: true
      output: Strings+Generated.swift

You should know a few important things about what you added here:

  1. When you convert your strings files, you should use only one of the localized directories. For each language added, a new localized directory gets created. In this project, the only localization is English. If you add more languages, there’s no need to modify this entry to pick up those additional translations. Because the strings file should have a matching set of keys and values, you’ll reference the translations like you would if you weren’t using SwiftGen.
  2. You added a new parameter, named publicAccess. If you look around any of the generated files you’ve added, you notice all the types have the access modifier of internal. By using this parameter, you can change the access modifier of the generated enumerations to be public.

Build the project, then add Strings+Generated.swift to the Generated group. Before you convert all the strings in the app, it’s important to understand how this file is a bit different.

Understanding the File

Open Strings+Generated.swift. You should see the parent type created, named L10n. In this enum, you’ll see several subtypes generated inside of it: DrinkDetail, Navigation and DrinkList. These correspond with the strings declared in Localizable.strings.

Open Localizable.strings and see how it declares the first entry:

"DrinkList.Navigation.Title" = "Drinks";

Notice how the key is declared using a dot-namespace notation:

  • DrinkList: This indicates this string belongs on the Drink List screen.
  • Navigation: Indicates this string is to be used in the navigation bar.
  • Title: Finally, this indicates it’s the title in the navigation bar.

Now, you may choose to organize and name your strings differently. There’s nothing wrong with that — this naming style is used to show how SwiftGen will convert and organize your code. For each period you place in your string, SwiftGen generates an additional subtype.

Back in Strings+Generated.swift, you’ll see the static function drinksCount. SwiftGen makes it easy to work with pluralized strings. Instead of having to create references to localized strings and using string formatters, these generated functions make it easy to use a function that takes your pluralized string’s values.

Now, convert all the localized strings used in the app to point to the generated types. Start by opening DrinksListView.swift. Next, find the line of code:

Text("DrinkList.Navigation.Title")

Change this to:

Text(L10n.DrinkList.Navigation.title)

Wait a second … what is L10n? This is a shortcut for “localization”. You might have also seen “internationalization” abbreviated as i18n. If you count the letters between the first letter and last “n” in either word, you find 10 or 18 letters, respectively. Although this makes sense, wouldn’t it be nice to use a different name for your top-level strings type?

Open swiftgen.yml and add a property to your strings entry, right after publicAccess, so it looks like this:

strings:
  inputs:
    - en.lproj
  outputs:
    - templateName: structured-swift5
      params:
        publicAccess: true
        enumName: Strings
      output: Strings+Generated.swift

Here, you added the parameter enumName. This lets you change the type from “L10n” to “Strings.”

Build and run. This time, you should have a compilation error. This is because the type L10n is no longer available. Go to DrinksListView.swift and find:

Text(L10n.DrinkList.Navigation.title)

Now, replace it with:

Text(Strings.DrinkList.Navigation.title)

Now, your app is using the new type name you provided in the previous step.

Build your app. You should no longer have any compilation errors.

Note: If you still are getting errors, you might need to clean your project. Select Product ▸ Clean Build Folder, then build again.

Next find the property drinkCountString. This is the code that uses the stringsdict file to handle how to display the count of drinks on the list. Replace it with the following:

private var drinkCountString: String {
  Strings.drinksCount(drinkStore.drinks.count)
}

If you compare it to the code that was there before, you can see this is a much quicker way to reference pluralized strings.

You should convert all the strings in the project away from using strings. Open Localizable.strings and look at all the string keys. You should find each usage of these keys in a Swift file and swap it for the variables generated by SwiftGen.

When you get to swapping the rating text found in DrinkDetailView.swift, it will use a function to provide the string, like how you handled the drink count.