An introduction to Tuist
Generative work of art for added production value lol.
Tuist is a tool to generate Xcode project files from a declarative description. These are the .xcodeproj
and .xcworkspace
files.
Index
Why Tuist?
Tuist exists to solve challenges related to working with Xcode.
- Complexity. Xcode projects contain numerous settings. You set them from various places of the the UI, and store them in many proprietary files. This is prone to errors, inconsistencies, and difficult to track in version control.
-
Compilation speed slows down in large projects. This is due to exponential complexity in the type checker. Contributing factors are type inference, overloading, and type constraint solving, among others. The solution is to split the project into smaller parts. This also enforces modularity and allows us to focus on individual subprojects.
-
Lack of templates. Xcode uses a template language called GYB (Generate Your Own Boilerplate). I found GYB challenging to work with, which is perhaps why it is not exposed.
-
Implicit builds. Xcode makes default decisions that simplify work. But, they also hide the build process. Declarative explicit files offer more control. For instance, we can switch SPM to XCFrameworks with few changes. This will significantly speed up compilation.
Now the good news. Most settings in a project are default settings. This means a declarative text file with minimal length could solve the problem. That’s what Tuist is going to provide.
Installing Tuist
The recommended installation uses mise, a package manager similar and compatible with brew.
So first install mise.
Then install Tuist.
.mise.toml
I was working with a project that uses an old version of Tuist. Instead of changing the global version, I found out that mise lets you use a different version per folder!.
Here is how it works. The version is set on a file named .mise.toml
in the project’s root directory. For instance:
Next Tuist invocation will install and execute the version configured:
The trick is that the tuist
command in the path is a “shim.” A shim is a command that passes arguments to the real tool. But, it also does other jobs, like version management.
The trick is that the tuist
command in the path is actually a mise “shim” that checks the configuration file and invokes the real tuist
command. A shim is a command that passes arguments to the real tool, but performs other functions, in this case, version management.
Hello World!
Tuist has one (1) template. Let’s run it.
And finally, generate the project files.
Running this opens Xcode automatically. We run the project, and get an iOS Hello World. Success!
Editing
To edit the project definition run tuist edit
from the terminal:
This opens your Tuist project definition in Xcode. Tuist uses Swift files to gain advantage of Xcode autocomplete. I trust you can figure out what each element is (“target name” is the name of the target and so on).
From here your mission is to use autocomplete, some common sense, and the Tuist reference. Everything is what it seems. You can also cheat and ask Claude/GPT about it. There is a chance they will answer using old Tuist syntax. You can also check the example projects in the documentation reference. They are at the bottom of the left column.
Dependencies
SPM
Let’s create a couple of packages using SPM:
Now run tuist edit
to edit the Project.swift
. Add the following changes:
Now generate again (tuist generate
) and you’ll see the dependencies in Xcode. What have we accomplished? Our project definition is now declarative and small (Project.swift
). This greatly simplifies managing our project.
Switch to Dynamic Frameworks
Edit the Project.swift
Edit the Tuist/Package.swift
Now run
Switch to XCFramework
Same configuration as dynamic frameworks but run
Go back to dynamic frameworks
Templates
This Hello World is going so well that I’m going to turn it into a template. First I’m going to copy a few folders and files to a hidden folder (~/.tuist/Templates/Hello`). “Hello” will be the name of the template I’m creating.
Templates have a definition file with the same name as the folder. Mind the comments.
So far this template is a big copy paste. For more sophisticated processes you can use StencilSwiftKit variables. Let’s do a bit of that. I’m going to replace the name: "Fruit"
string in the template with the name
parameter we added before. This should change the name of the project and the main target.
I think my template is ready. Let’s try it.
It does work!
Almost perfect!
I missed those two names. This template needs a bit more work. But first, I have to finish the Fruit project.
Subprojects
I noticed Xcode takes a while to open SPM packages with many files. This is an inconvenience when I’m switching branches. I want my Fruit project to avoid that. I’m going to move the dependencies from Project.swift
to Tuist/Package.swift
. Any Project.swift
in the project will see those dependencies. This reminds me, it’s possible to have more than one Project.swift file. They will all appear as subprojects in the workspace.
Changes to Tuist/Package.swift
:
Changes to the Project.swift
:
Run tuist generate
and the packages remain there, but they are now dynamic frameworks.
Dependencies as dynamic frameworks
And now for the final Tuist trick, run the following:
Now dependencies are XCFrameworks! That means the same binary for simulator and device. No package indexing or compilation is needed. We could even distribute these binaries to other developers. Binary repository solutions like Nexus can assist with that.
Dependencies as XCFrameworks
One last tip: It’s not uncommon for SwiftUI previews to stop working in complex projects. This happens because SwiftUI uses a different, lighter build system. Tuist will likely solve it if you build an explicit dependency tree. That is, add dependencies and their child dependencies to Tuist/Package.swift
.
Conclusion
In this article we explored the challenges of working with Xcode. We installed Tuist, created a simple project, and edited its definition. Then, we converted it into a template. We also explored SPM, dynamic frameworks, and even binary XCFrameworks. Clearly, we can apply this knowledge to improve speed and maintenance in our projects.