Tuist init: How to use Tuist templates to bootstrap your project
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
.
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.
- 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
andswiftui
. 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
.
- 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.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
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.
- 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
- 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.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
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.
- 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. - It should contain name, and platform attributes to align with the official templates.
- 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 ShareTuist 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.
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.