Tuist workspaces help you organize multiple projects into a cohesive development environment, enabling modular architecture and efficient team collaboration.
Understanding Workspaces
An Xcode workspace is a container that groups multiple projects, allowing them to work together while maintaining independence.
Without Workspace Single project, all targets in one .xcodeproj
With Workspace Multiple projects organized in one .xcworkspace
When to Use Workspaces
Single Project
Multiple Projects
Use a single Project.swift when:
You have a simple app structure
All targets are closely related
Team is small (< 5 developers)
Build times are acceptable
Tuist automatically creates a workspace for single projects - no Workspace.swift needed!
Use Workspace.swift when:
Modular architecture with independent features
Large codebase split across teams
Want to optimize build times
Need to share schemes across projects
/MyWorkspace
├── Workspace.swift
├── App/Project.swift
├── Features/
│ ├── Profile/Project.swift
│ └── Settings/Project.swift
└── Core/Project.swift
Creating a Workspace
Define a workspace in Workspace.swift:
Basic Workspace
Advanced Workspace
import ProjectDescription
let workspace = Workspace (
name : "MyWorkspace" ,
projects : [
"App" ,
"Framework" ,
"Core"
]
)
Workspace Structure Patterns
Feature-Based
Layer-Based
Micro-Features
Monorepo
Organize by product features: /MyWorkspace
├── Workspace.swift
├── App/
│ └── Project.swift # Main app
├── Features/
│ ├── Authentication/
│ │ └── Project.swift # Auth feature
│ ├── Profile/
│ │ └── Project.swift # Profile feature
│ └── Settings/
│ └── Project.swift # Settings feature
└── Core/
└── Project.swift # Shared utilities
let workspace = Workspace (
name : "MyApp" ,
projects : [
"App" ,
"Features/**" ,
"Core"
]
)
Organize by architectural layers: /MyWorkspace
├── Workspace.swift
├── Application/
│ └── Project.swift # UI layer
├── Domain/
│ └── Project.swift # Business logic
├── Data/
│ └── Project.swift # Data access
└── Infrastructure/
└── Project.swift # Network, storage
This follows Clean Architecture principles with clear dependency direction
Fine-grained modularization: /MyWorkspace
├── Workspace.swift
├── App/Project.swift
└── Modules/
├── ProfileFeature/
│ ├── Interface/Project.swift # Public API
│ ├── Implementation/Project.swift # Implementation
│ └── Testing/Project.swift # Test utilities
└── SettingsFeature/
├── Interface/Project.swift
├── Implementation/Project.swift
└── Testing/Project.swift
Multiple apps in one workspace: /MyWorkspace
├── Workspace.swift
├── Apps/
│ ├── CustomerApp/Project.swift
│ ├── AdminApp/Project.swift
│ └── InternalTools/Project.swift
├── Shared/
│ ├── UI/Project.swift
│ ├── Networking/Project.swift
│ └── Analytics/Project.swift
└── Services/
├── Authentication/Project.swift
└── API/Project.swift
Cross-Project Dependencies
Link targets across projects in the workspace:
App/Project.swift
Features/Profile/Project.swift
import ProjectDescription
let project = Project (
name : "App" ,
targets : [
. target (
name : "App" ,
destinations : . iOS ,
product : . app ,
bundleId : "com.example.app" ,
sources : [ "Sources/**" ],
dependencies : [
// Reference targets from other projects
. project ( target : "Profile" , path : "../Features/Profile" ),
. project ( target : "Settings" , path : "../Features/Settings" ),
. project ( target : "Core" , path : "../Core" )
]
)
]
)
Avoid circular dependencies between projects. Tuist will detect and report these during generation.
Workspace Schemes
Create schemes that span multiple projects:
import ProjectDescription
let workspace = Workspace (
name : "MyWorkspace" ,
projects : [ "App" , "Features/**" , "Core" ],
schemes : [
// Build all features
. scheme (
name : "AllFeatures" ,
shared : true ,
buildAction : . buildAction (
targets : [
. project ( path : "Features/Profile" , target : "Profile" ),
. project ( path : "Features/Settings" , target : "Settings" ),
. project ( path : "Features/Analytics" , target : "Analytics" )
]
),
testAction : . targets (
[
. testableTarget ( target : . project ( path : "Features/Profile" , target : "ProfileTests" )),
. testableTarget ( target : . project ( path : "Features/Settings" , target : "SettingsTests" ))
],
options : . options ( coverage : true )
)
),
// Integration tests
. scheme (
name : "Integration" ,
shared : true ,
buildAction : . buildAction (
targets : [
. project ( path : "App" , target : "App" )
]
),
testAction : . targets (
[
. testableTarget ( target : . project ( path : "App" , target : "IntegrationTests" ))
]
)
),
// CI scheme
. scheme (
name : "CI" ,
shared : true ,
buildAction : . buildAction (
targets : [
. project ( path : "App" , target : "App" ),
. project ( path : "Core" , target : "Core" )
]
),
testAction : . targets (
[
. testableTarget ( target : . project ( path : "App" , target : "AppTests" )),
. testableTarget ( target : . project ( path : "Core" , target : "CoreTests" ))
],
options : . options (
coverage : true ,
codeCoverageTargets : [
. project ( path : "App" , target : "App" ),
. project ( path : "Core" , target : "Core" )
]
)
)
)
]
)
Additional Files
Include documentation and configuration files in the workspace:
additionalFiles : [
// Documentation
"README.md" ,
"Documentation/**" ,
// Configuration
".swiftlint.yml" ,
".swiftformat" ,
// CI/CD
".github/**" ,
// Folder references (blue folders)
. folderReference ( path : "Design" )
]
Additional files appear in Xcode’s project navigator but aren’t built - perfect for documentation!
Workspace Generation Options
Customize workspace behavior:
generationOptions : . options (
// Disable auto-generated workspace schemes
autogeneratedWorkspaceSchemes : . disabled ,
// Enable synthetic projects for Swift packages
renderMarkdownReadme : true
)
Best Practices
Keep Dependencies Unidirectional
Ensure dependencies flow in one direction: App → Features → Core
↘ ↗
Services
Avoid: App ⇄ Features ❌ (circular dependency)
Use protocols/interfaces to invert dependencies when needed: // In Core
protocol AnalyticsService {}
// In Features/Analytics
class FirebaseAnalytics : AnalyticsService {}
// In App
let analytics: AnalyticsService = FirebaseAnalytics ()
Use Focused Workspaces for Large Teams
Create team-specific workspaces: # Mobile team workspace
/Mobile/Workspace.swift
# Backend team workspace
/Backend/Workspace.swift
# Shared infrastructure
/Shared/Workspace.swift
Structure projects to minimize rebuilds:
Small, focused projects - Changes affect fewer targets
Static frameworks - Faster linking than dynamic
Explicit dependencies - Avoid unnecessary rebuilds
. target (
name : "Core" ,
product : . staticFramework , // Fast linking
dependencies : [] // Minimal dependencies
)
Document Your Architecture
Include architecture documentation in the workspace: additionalFiles : [
"Architecture.md" ,
"Documentation/ModuleGuide.md" ,
. folderReference ( path : "Documentation" )
]
Visualizing Your Workspace
Tuist can generate a visual graph of your workspace:
# Generate dependency graph
tuist graph
# Export as image
tuist graph --format png
# Skip external dependencies
tuist graph --skip-external-dependencies
Example output:
Migration Strategies
Monolith to Modular
Existing Workspace
Gradually extract features from a monolithic app:
Start with Core
Extract shared utilities first: /MyApp
├── App/Project.swift # Main app (monolith)
└── Core/Project.swift # Extracted utilities
Extract Features
Move features one at a time: /MyApp
├── Workspace.swift
├── App/Project.swift
├── Features/
│ └── Profile/Project.swift # First extracted feature
└── Core/Project.swift
Continue Extraction
Gradually move all features: /MyApp
├── Workspace.swift
├── App/Project.swift # Now just app shell
├── Features/
│ ├── Profile/Project.swift
│ ├── Settings/Project.swift
│ └── Analytics/Project.swift
└── Core/Project.swift
Migrate an existing Xcode workspace to Tuist:
Create Manifests
Write Project.swift for each existing project
Create Workspace Manifest
let workspace = Workspace (
name : "ExistingWorkspace" ,
projects : [
"ProjectA" ,
"ProjectB"
]
)
Generate & Compare
# Generate new workspace
tuist generate --no-open
# Compare with existing workspace
diff -r ExistingWorkspace.xcworkspace \
NewWorkspace.xcworkspace
Iterate
Adjust manifests until generated workspace matches
Example: Real-World Workspace
Here’s a complete example of a production workspace:
Workspace.swift
Directory Structure
import ProjectDescription
let workspace = Workspace (
name : "ShopApp" ,
projects : [
"App" ,
"Features/**" ,
"Services/**" ,
"Core"
],
schemes : [
. scheme (
name : "ShopApp-Development" ,
shared : true ,
buildAction : . buildAction (
targets : [. project ( path : "App" , target : "ShopApp" )]
),
testAction : . targets (
[
. testableTarget ( target : . project ( path : "App" , target : "ShopAppTests" )),
. testableTarget ( target : . project ( path : "Features/Catalog" , target : "CatalogTests" ))
],
options : . options ( coverage : true )
),
runAction : . runAction (
executable : . project ( path : "App" , target : "ShopApp" ),
arguments : . arguments (
environmentVariables : [
"API_URL" : "https://dev-api.shop.com"
]
)
)
)
],
additionalFiles : [
"README.md" ,
"Documentation/**"
]
)
Next Steps
Manifests Deep dive into manifest file structure
Generated Projects Understand project generation
Architecture Learn about Tuist’s architecture
Dependencies Managing external dependencies