Tuist Tutorial for Xcode

Learn how to use Tuist to create and manage complex Xcode projects and workspaces on-the-fly. By Mark Struzinski.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Generating Your First Project

OK, the time has come to use Tuist to generate your first project and workspace! Open Terminal again. Press Control-C or Command-. to stop the current editing session. Xcode will show you a message indicating the workspace no longer exists:

Xcode Close Workspace dialog

Click Close.

Before generating the project, you'll use Tuist's lint command to check the validity of your project manifest.

From Terminal, run the following:

tuist lint project

This command will lint the manifest file in the current directory.

You'll see output similar to this:

Loading the dependency graph
Loading project at /Users/ski081/Desktop/WiP
Running linters
Linting the environment
Linting the loaded dependency graph
No linting issues found

Next, in Terminal, enter the following:

tuist generate

You'll get output similar to the following:

Generating workspace MovieInfo.xcworkspace
Generating project MovieInfo
Project generated.
Total time taken: 0.579s

Now, run the following in Terminal to see the directory's new content:

ls -al

You'll see something like this:

drwxr-xr-x@ 10 ski081  staff   320 May 30 22:01 .
drwx------@ 26 ski081  staff   832 May 30 21:51 ..
-rw-r--r--@  1 ski081  staff  6148 May 29 08:50 .DS_Store
drwxr-xr-x@  4 ski081  staff   128 May 30 08:23 AdditionalTargetFiles
drwxr-xr-x   3 ski081  staff    96 May 30 22:01 Derived
drwxr-xr-x@  8 ski081  staff   256 May 30 21:50 MovieInfo
drwxr-xr-x@  6 ski081  staff   192 May 30 22:01 MovieInfo.xcodeproj
drwxr-xr-x@  6 ski081  staff   192 May 30 22:01 MovieInfo.xcworkspace
-rw-r--r--@  1 ski081  staff   409 May 30 22:00 Project.swift
drwxr-xr-x@  5 ski081  staff   160 May 30 08:24 config

Congratulations! You generated your first project and workspace. :]

Finally, enter the following to open the workspace:

xed .
Note: xed . opens a workspace in the current directory, if one exists. If not, it will open an Xcode project. So, in either case, it opens the "correct" file.

Build and run. The app will behave exactly as before, but there's a huge difference: You've removed the dependency on the project file and can now generate it whenever you want.

Introducing Focus Mode

Before you start adding additional features and projects through Tuist, there's another interesting feature to review: focus mode. Focus lets you open a single target and its dependencies. This lets you ignore any projects or dependencies you don't want to use at the time.

Try this out on your newly generated project setup. From Terminal, enter the following:

tuist focus MovieInfo

This tells Tuist to generate and open only the assets you need to work with MovieInfo. It will also remove unneeded assets from the Xcode cache and replace them with pre-compiled assets to ensure Xcode doesn't try to index them.

You'll see a message like this:

Generating workspace MovieInfo.xcworkspace
Generating project MovieInfo
Deleted persisted 1622124912.TuistAnalytics.45978508-3C7B-4A87-B3DD-1B5FD4476A55.json

Xcode will open with your target and all dependencies initialized. This isn't useful with a small project like MovieInfo, but you can see how it would benefit larger projects with multiple sub-projects and dependencies.

Adding Support for Settings Files

Next, you'll generate your project and target settings from external .xcconfig files. This is a best practice to give you more control over how Tuist builds the project.

This practice also isolates you from any changes you need to make to the project and target settings in the future. If you needed to change a build setting without an external configuration, Tuist would override it each time it regenerated the file. You'd then need to remember to make that change again in the project or target settings.

By pointing Tuist to an external source, you can make changes there and any new project generation commands will use them.

Tuist uses Settings to represent configurations that you can use in project and target objects. You'll use it to set up external configuration sources.

Begin another editing session from Terminal:

tuist edit

Open Project.swift and add the following just after the import:

// 1
let projectSettings = Settings(
  // 2
  debug: Configuration(xcconfig: Path("config/MovieInfoProject.xcconfig")),
  release: Configuration(xcconfig: Path("config/MovieInfoProject.xcconfig")),
  defaultSettings: .none)

This does the following:

  1. Declares a Settings object.
  2. Specifies .xcconfig files to use for debug and release configurations. These are already in the starter project, in the config folder.

Next, in the initialization of project, replace the settings parameter with your new object:

settings: projectSettings,

Next, you'll apply settings to your target. Right under projectSettings, create targetSettings:

let targetSettings = Settings(
  debug: Configuration(xcconfig: Path("config/MovieInfoTarget.xcconfig")),
  release: Configuration(xcconfig: Path("config/MovieInfoTarget.xcconfig")),
  defaultSettings: .none)

Now update the settings parameter in the initialization of the Target object, to the following:

settings: targetSettings)

Save Project.swift and exit with Control-C in Terminal. Now, generate your project again with:

tuist generate

Open the workspace. Build and run. The app looks the same as before, but now your build settings live outside of the project so you can apply the same settings every time you generate the project with Tuist.

Setting up External Dependencies

Your next goal is to set up external framework dependencies in the Tuist manifest file. This lets you automatically set up and link any framework code you already have on disk to your project. No more dragging and dropping and fussing with Xcode build settings!

Imagine you've decided to move the networking functionality out of the iOS codebase so you can share it with multiple targets, possibly for a future macOS or watchOS app. The easiest way to do this is to extract the network layer and turn it into a dynamic framework in your project.

Moving Files Into Place

First, you'll need to move the relevant files into a separate location. This lets you reference them easily from the Tuist manifest file.

The starter project has a directory named AdditionalTargetFiles. Move NetworkKit from inside this directory to the root of the project. It should sit alongside the MovieInfo directory and Project.swift.

Once you finish, your directory structure should look like this:

NetworkKit file structure

Move the Network directory at MovieInfo/Source/Network and all its contents to the trash.

Generating the Framework From Tuist

In Terminal, switch to the NetworkKit directory and run the edit command:

cd NetworkKit
tuist edit

As before, this opens a project in Xcode. Select Project.swift. You'll see a blank file with an import at the top.

Now, enter the following:

let projectSettings = Settings(
  debug: Configuration(xcconfig: Path("config/NetworkKitProject.xcconfig")),
  release: Configuration(xcconfig: Path("config/NetworkKitProject.xcconfig")),
  defaultSettings: .none)

let targetSettings = Settings(
  debug: Configuration(xcconfig: Path("config/NetworkKitTarget.xcconfig")),
  release: Configuration(xcconfig: Path("config/NetworkKitTarget.xcconfig")),
  defaultSettings: .none)

let project = Project(
  name: "NetworkKit",
  organizationName: "Ray Wenderlich",
  settings: projectSettings,
  targets: [
    Target(
      name: "NetworkKit",
      platform: .iOS,
      product: .framework,
      bundleId: "<YOUR_BUNDLE_ID_HERE>",
      infoPlist: "NetworkKit/Info.plist",
      sources: ["NetworkKit/Source/**"],
      settings: targetSettings)
  ])

Don't forget to replace <YOUR_BUNDLE_ID_HERE> with your own values. The bundle ID must be different from the bundle ID you're using for the MovieInfo app.

The code above is similar to your main project definition, with one key difference: Notice that product in the Target initializer declares a framework instead of an app product. This generates a dynamic iOS framework.

Save this file and enter Control-C in Terminal to end the editing session.