Tuist init: How to use Tuist templates to bootstrap your project

⋅ 10 min read ⋅ Tuist Template Scaffold Xcode Development

Table of Contents

Part 2 in the series "Tuist templates and how to use them". In the second part, we will discuss one of the two ways to use Tuist templates, tuist init.

  1. Template
  2. Tuist Init
  3. Tuist Scaffold
  4. What is the different between Tuist init and scaffold

In the last post, we learn how to create Tuist's template but didn't talk much about using them. In the article, we will discuss one way to use Tuist templates. The tuist init command.

How can we use templates

There are two ways to use templates with Tuist.

  1. tuist init command. This is a command that intends to bootstrap a new application project. It should generate all necessary files for the iOS project and Tuist itself, such as the Info.plist files, an AppDelegate.swift, a tests file, and a Project.swift that contains the project's definition. Tuist provided two templates for you, default and swiftui. In this article, we will focus on this type of initiation.
  • Bootstrap a new project using UIKit: tuist init.
  • Bootstrap a new project using SwiftUI: tuist init --name MyApp --template swiftui.
  1. tuist scaffold command. This is a command for existing projects. For projects with an established architecture, developers might want to bootstrap new components or features that are consistent with the project. The following are scenarios where scaffolding might be useful:
  • Create a new feature that follows a given architecture: tuist scaffold viper --name MyFeature.
  • Create new projects: tuist scaffold feature-project --name Home.

Both init and scaffold shared the same functionality, which is using a template to generate files. The only thing that differentiates them apart is the purpose of the templates that I mentioned above.

Tuist is not opinionated about the content of your templates and what you use them for. They are only required to be in a specific directory, with a Template.swift manifest file that describes it. Thus, both init and scaffold can use the same templates because the template is just a way to create and move files over at the end of the day.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Tuist Init command

As you know, tuist init is for bootstrap the project. We will create a template based on my previous post.

The final project

I will convert my previous project into a template. Here is a recap of our project structure. If you not familiar with Tuist, I suggest you check two of my latest posts (Getting Started with Tuist and Add test target) first, then come back once you finished.

File structure:

Tests
- MyAppTests.swift
Sources
- ContentView.swift
- MainApp.swift
Project.swift

MainApp.swift

import SwiftUI

@main
struct MainApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

ContentView

import SwiftUI

struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

MyAppTests.swift

import XCTest
@testable import MyApp

final class MyAppTests: XCTestCase {

}

Project.swift

import ProjectDescription

let project = Project(
name: "MyApp",
targets: [
Target(
name: "MyApp",
platform: .iOS,
product: .app,
bundleId: "com.sarunw.myapp",
infoPlist: .extendingDefault(
with: [
"UILaunchScreen": [:]
]
),
sources: [
"Sources/**"
]
),
Target(
name: "MyAppTests",
platform: .iOS,
product: .unitTests,
bundleId: "com.sarunw.myappTests",
infoPlist: .default,
sources: [
"Tests/**"
],
dependencies: [
.target(name: "MyApp")
]
)
]
)

These files will generate a simple SwiftUI project with two targets, the main app, and the test target. We will start by converting all this into a template. In the end, you should be able to run a single command to generate all of these files and folders.

Create a template

We learn how to create a template in the last article. You will see how easy it is to put that into use. Let's pack our project into a template.

  1. First, we move all Swift files to our template directory as is.
Tuist
- Templates
- sarunw
- sarunw.swift
- Project.swift
- MainApp.swift
- ContentView.swift
- AppTests.swift
  1. In our manifest file, sarunw.swift, we will copy everything over with .file.

sarunw.swift

import ProjectDescription

let nameAttribute: Template.Attribute = .required("name")

// 1
let projectPath = "."
let appPath = "Sources/"
let testPath = "Tests/"

let template = Template(
description: "Custom template",
attributes: [
nameAttribute
],
files: [
.file(path: projectPath + "/Project.swift", templatePath: "Project.swift"), // 2
.file(path: appPath + "/MainApp.swift", templatePath: "MainApp.swift"),
.file(path: appPath + "/ContentView.swift", templatePath: "ContentView.swift"),
.file(path: testPath + "/AppTests.swift", templatePath: "AppTests.swift")
]
)

<1> We declare variables for each destination path. This is for a convenient purpose when copying files.
<2> We copy files to different destination based on our final result structure.

Using Tuist init command

Try running the tuist init command, and you will get the following error.

tuist init -t sarunw
Can't initialize a project in the non-empty directory at path /path/to/project.

This is the only significant difference between the init and scaffold command. Init command means to bootstrap a new project, and this restriction conveys that intention.

How to use a custom template with init command

To use a template with an init command, you need to know how Tuist finds the template files. In my last post about how to create Tuist's template, we learn that we have to declare our template under the Tuist/Templates directory, but what I didn't mention is it doesn't have to be in the current directory.

Tuist traverses up the directories hierarchy until it finds a Tuist directory.
– From Tuist documentation

Try moving our template out from the current directory up one level.

From this:

Projects
- MyApp
- Tuist
- Templates
- sarunw
- sarunw.swift
- Project.swift
- MainApp.swift
- ContentView.swift
- AppTests.swift
- MyApp2
...

To this:

Projects
- Tuist
- Templates
- sarunw
- sarunw.swift
- Project.swift
- MainApp.swift
- ContentView.swift
- AppTests.swift
- MyApp
- MyApp2
...

Try running the init command again. This time the project will generate correctly.

tuist init -t sarunw
Project generated at path /Projects/MyApp.
Projects
- Tuist
- Templates
- sarunw
- sarunw.swift
- Project.swift
- MainApp.swift
- ContentView.swift
- AppTests.swift
- MyApp
- Project.swift
- MainApp.swift
- ContentView.swift
- AppTests.swift
- MyApp2
...

Confirm that everything generates correctly with tuist generate and try running the app.

Make it feel official

Now that we have a working template, it's time to improve it and make it feel like the official template.

If you run tuist init --help, you will see the following description.

tuist init --help
OVERVIEW: Bootstraps a project

USAGE: tuist init [--platform <platform>] [--name <name>] [--template <template>] [--path <path>]

OPTIONS:
--help-env Display subcommands to manage the environment tuist
versions.
--platform <platform> The platform (ios, tvos or macos) the product will be
for (Default: ios)
-n, --name <name> The name of the project. If it's not passed (Default:
Name of the directory)
-t, --template <template>
The name of the template to use (you can list
available templates with tuist scaffold list)
-p, --path <path> The path to the folder where the project will be
generated (Default: Current directory)
-h, --help Show help information.

So, users might be expected your template to support these options. You don't need to worry about -t and -p since those two are handle by Tuist. But you might want to make your template support -n and --platform if possible to make it align with the tuist init description.

So you might want to add name and platform in your template attributes.

import ProjectDescription

let nameAttribute: Template.Attribute = .required("name") // 1
let platformAttribute: Template.Attribute = .optional("platform", default: "ios") // 2

let projectPath = "."
let appPath = "Sources/"
let testPath = "Tests/"

let template = Template(
description: "Custom template",
attributes: [
nameAttribute, // 3
platformAttribute // 4
],
files: [
.file(path: projectPath + "/Project.swift", templatePath: "Project.swift"),
.file(path: appPath + "/MainApp.swift", templatePath: "MainApp.swift"),
.file(path: appPath + "/ContentView.swift", templatePath: "ContentView.swift"),
.file(path: testPath + "/AppTests.swift", templatePath: "AppTests.swift")
]
)

<1>, <2> Declare two attributes that align with tuist init --help.
<3>, <4> Use them in attributes.

Improvment

Let's improve our template with attributes and a dynamic template. Right now, our Project.swift hard code app name and platform, we will make it use the passing parameters instead.

Project.stencil

import ProjectDescription

let project = Project(
name: "{{ name }}",
targets: [
Target(
name: "{{ name }}",
platform: .{{ platform }},
product: .app,
bundleId: "com.sarunw.{{ name }}",
infoPlist: .extendingDefault(
with: [
"UILaunchScreen": [:]
]
),
sources: [
"Sources/**"
]
),
Target(
name: "{{ name }}Tests",
platform: .{{ platform }},
product: .unitTests,
bundleId: "com.sarunw.{{ name }}Tests",
infoPlist: .default,
sources: [
"Tests/**"
],
dependencies: [
.target(name: "{{ name }}")
]
)
]
)

We rename Project.swift to Project.stencil and replace all hard code names and platforms with passing arguments.

Then we change our manifest file to use this new stencil.

import ProjectDescription

let nameAttribute: Template.Attribute = .required("name")
let platformAttribute: Template.Attribute = .optional("platform", default: "ios")

let projectPath = "."
let appPath = "Sources/"
let testPath = "Tests/"

let template = Template(
description: "Custom template",
attributes: [
nameAttribute,
platformAttribute
],
files: [
.file(path: projectPath + "/Project.swift", templatePath: "Project.stencil"), // 1
.file(path: appPath + "/MainApp.swift", templatePath: "MainApp.swift"),
.file(path: appPath + "/ContentView.swift", templatePath: "ContentView.swift"),
.file(path: testPath + "/AppTests.swift", templatePath: "AppTests.swift")
]
)

<1> Use .stencil instead of .swift.

Run tuist init -t sarunw, and everything works the same way it did, but this time our template is ready to adapt to name and platform changes.

Tuist Scaffold

We will talk about scaffold in the next article, but I want to show you a little bit of scaffold before ending this article.

If you try to delete everything and use the template again using tuist scaffold, you will get the following error.

tuist scaffold sarunw

Error: Missing expected argument '--name <name>'
Usage: tuist scaffold <template> [--json] [--path <path>] --name <name> [--platform <platform>] <subcommand>
See 'tuist scaffold --help' for more information.

Name is a required attribute, so we get a warning for the missing argument. Try again with the name attribute.

tuist scaffold sarunw --name MyApp

Template sarunw was successfully generated

You will get the project generated exactly the same as tuist init.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Conclusion

In this article, you learn one way of using a template, tuist init. There are some behaviors that I want you to remember about this command.

  1. The directory needs to be empty. This conveys the intention of the command that uses to bootstrap the project. Based on this fact, you need to put Tuist/Templates up in the directories hierarchy for it to work.
  2. It should contain name, and platform attributes to align with the official templates.
  3. The name attribute defaults to the current directory.

And the last point that I want to highlight is, both tuist init and tuist scaffold can use the same template files. It different lie in the behaviors that you see above. In the next article, I will talk about the tuist scaffold in more detail.


Read more article about Tuist, Template, Scaffold, Xcode, Development, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
Tuist Template: What is it and how to create them

The template is a way to group repetitive code structure into a reusable component. You will learn how to create them in this article.

Next
How to fix ZStack's views disappear transition not animated in SwiftUI

Show and hide transition animation in ZStack can be glitchy. Learn how to fix it with a simple trick.

← Home