SwiftGen Tutorial for iOS
Learn how SwiftGen makes it easy to get rid of magic strings in your iOS projects. By Andy Pereira.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SwiftGen Tutorial for iOS
30 mins
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 seeaccessModifier
defined as a variable. By default, the access modifier isinternal
. If you remember from before, you saw how you can change this topublic
- The rest of this is actually just standard code. It creates a SwiftUI color from a UIKit color.
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:
-
mappedFont(_:textStyle:)
creates a custom font from a name andTextStyle
. The style is used to get the standard, default font size each font should be. -
mapToUIFontTextStyle(_:)
simply provides a 1:1 mapping of a SwiftUITextStyle
to a UIKitTextStyle
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:
- NotoSans
- 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!