Build Templates For Code Generation With Tuist

Ronan O Ciosoig
5 min readSep 12, 2021

Take advantage of the tuist code generation feature to build a scaffold for a new module or screen

If you are not familiar with tuist have a look at their documentation and some of the articles linked here. The Github repo related to this article is here.

Code Generator

At the core of tuist is code generation, and in this article I am going into more details of the scaffold command, create a template to generate a module, and use it to further refactor the project.

In the last article I went through a refactor of multiple projects into one single tuist project. That change makes the project easier to manage and manipulate. The project contains 6 modules, but what about adding another one? How would I go about it? I would make a template of course and run the tuist scaffold command.

First I take a look at the folder and file structure of an existing module. The Home module is structured like this:

Almost all of the files in the example app are generic. The example app needs the app icon and the LaunchScreen uses the ball icon, thus these 2 image assets are added to the XCAssets. The resources for each module is specific to what is required so I will skip that. Then the scene could have a generic view controller for example, and a unit test could be generated for that. The LaunchScreen displays the name of the module at startup so this is defined as a stencil as well and then the module name is injected.

The scaffold command looks for a swift file in a folder under Tuist/Templates. For this template it looks like this:

Template Source

The template source can define attributes (required or optional) that will be used to replace the placeholders in stencil files.

It then iterates through all the stencil files, doing a string replace with the parameters provided and places the file in the path defined in the template. It can also copy entire folders as is into the new location. It’s really that simple. For example:

  .directory(
path: “\(nameAttribute)/Example”,
sourcePath: “Resources”
),
.file(
path: “\(nameAttribute)/Example/Resources/LaunchScreen.storyboard”,
templatePath: “LaunchScreen.stencil”
)

The typical Swift source header has the following structure:

//
// AppDelegate.swift
// {{ name }}ExampleApp
//
// Created by {{ author }} on {{ date }}.
// Copyright © {{ year }} {{ company}}. All rights reserved.
//

In the template the name and author are defined as required.

let nameAttribute: Template.Attribute = .required(“name”)
let authorAttribute: Template.Attribute = .required(“author”)

The company is hardcoded in the template and the year and date are generated.

var defaultYear: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = “yyyy”
return dateFormatter.string(from: Date())
}
var defaultDate: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = “dd/MM/yyyy”
return dateFormatter.string(from: Date())
}
let yearAttribute: Template.Attribute = .optional(“year”, default: defaultYear)let dateAttribute: Template.Attribute = .optional(“date”, default: defaultDate)

Give It A Spin

There’s no better way to validate a process than to go and actually do it. To do this I am going to refactor the Backpack module and split out the detail scene into a new module called Detail.

1: Run the command to generate the module

tuist scaffold module — name Detail — author “Ronan” — path Features

2: Edit the project and add the module

Run tuist edit and add the new module.

Module(name: “Detail”,
path: “Detail”,
frameworkDependancies: [.target(name: “Common”),
.target(name: “Haneke”)],
exampleDependencies: [],
frameworkResources: [],
exampleResources: [“Resources/**”]),

It has similar dependancies as Backpack so I just copy that and change the name .Then tuist focus DetailExampleApp opens and compiles the new target. At this point the example app has the app icon in place, a launch screen with the ball image, and the name of the module, and then on launch the app displays the name of the module in the centre of the screen. There’s no effort in adding a new app using this approach.

3: Move the code

I move the scene from the Backpack module to the new one, then check the target compiles — it does. Since it contains a storyboard, the framework resource path needs to be added for it to load in the module manifest.

frameworkResources: [“Sources/**/*.storyboard”]

4: Update the Example App

The example app in this case needs to display the details of a Pokemon and for this it needs some mock data provided. This mock data is already in the Backpack module so I just copy MockDataFactory.swift from the example app for that target. Then the last step is that the view needs to be displayed in the coordinator.

5: Update the BackpackUI Example App

The BackpackUIExampleApp target needs to know about the Detail module so the project manifest requires this:

exampleDependencies: [.target(name: “Detail”)]

Then for it to run, the coordinator of the example app needs import Detail. Works!

6: Update the Pokedex App

The Pokemon app target now needs to import the Detail module so that the Coordinator knows how to load the scene. Add import Detail in the coordinator. Run. It works. How incredibly simple was that?

The simulator now looks like this screen shot, with the project consisting of 7 micro apps along with the main one.

Recap

In this article I created a template that the scaffold command builds into a project that provides code for a working example app, which has an app icon, and launch screen, and a simple view controller. This makes it a breeze to continue to further modularise an app into small parts of functionality.

When you have a project defined with the help of tuist, and a module template defined, adding new modules is really a snap with the scaffold command. There is no need to do any complex refactor, or change the Xcode project settings to import the framework — tuist generates the project every time so that it is configured correctly. Also, for a team of multiple developers, you don’t have to deal with Xcode project merges which can be horrendous. What is typically a tedious task can be done in a matter of minutes with convenience to the tuist toolset.

The completed code for this article is here.

Please follow to know about the upcoming articles.

--

--

Ronan O Ciosoig

iOS software architect eDreams ODIGEO and electronic music fan