SwiftGen Integration — Expert Decisions
Expert decision frameworks for SwiftGen choices. Claude knows asset catalogs and localization — this skill provides judgment calls for when SwiftGen adds value and configuration trade-offs.
Decision Trees
When SwiftGen Adds Value
Should you use SwiftGen for this project? ├─ > 20 assets/strings │ └─ YES — Type safety prevents bugs │ Typos caught at compile time │ ├─ < 10 assets/strings, solo developer │ └─ MAYBE — Overhead vs. benefit │ Quick projects may not need it │ ├─ Team project with shared assets │ └─ YES — Consistency + discoverability │ Autocomplete reveals available assets │ ├─ Assets change frequently │ └─ YES — Broken references caught early │ CI catches missing assets │ └─ CI/CD pipeline exists └─ YES — Validate assets on every build Prevents runtime crashes
The trap: Using SwiftGen on tiny projects or for assets that rarely change. The setup overhead may exceed the benefit.
Template Selection
Which template should you use? ├─ Strings │ ├─ Hierarchical keys (auth.login.title) │ │ └─ structured-swift5 │ │ L10n.Auth.Login.title │ │ │ └─ Flat keys (login_title) │ └─ flat-swift5 │ L10n.loginTitle │ ├─ Assets (Images) │ └─ swift5 (default) │ Asset.Icons.home.image │ ├─ Colors │ └─ swift5 with enumName param │ Asset.Colors.primary.color │ ├─ Fonts │ └─ swift5 │ FontFamily.Roboto.bold.font(size:) │ └─ Storyboards └─ scenes-swift5 StoryboardScene.Main.initialViewController()
Asset Organization Strategy
How should you organize assets? ├─ Small app (< 50 assets) │ └─ Single Assets.xcassets │ Feature folders inside catalog │ ├─ Medium app (50-200 assets) │ └─ Feature-based catalogs │ Auth.xcassets, Dashboard.xcassets │ Multiple swiftgen inputs │ ├─ Large app / multi-module │ └─ Per-module asset catalogs │ Each module owns its assets │ Module-specific SwiftGen runs │ └─ Design system / shared assets └─ Separate DesignSystem.xcassets Shared across targets
Build Phase Strategy
When should SwiftGen run? ├─ Every build │ └─ Run Script phase (before Compile Sources) │ Always current, small overhead │ ├─ Only when assets change │ └─ Input/Output files specified │ Xcode skips if unchanged │ ├─ Manual only (CI generates) │ └─ Commit generated files │ No local SwiftGen needed │ Risk: generated files out of sync │ └─ Pre-commit hook └─ Lint + generate before commit Ensures consistency
NEVER Do
Configuration
NEVER hardcode paths without variables:
❌ Breaks in different environments
strings: inputs: /Users/john/Projects/MyApp/Resources/en.lproj/Localizable.strings outputs: output: /Users/john/Projects/MyApp/Generated/Strings.swift
✅ Use relative paths
strings: inputs: Resources/en.lproj/Localizable.strings outputs: output: Generated/Strings.swift
NEVER forget publicAccess for shared modules:
❌ Generated code is internal — can't use from other modules
xcassets: inputs: Resources/Assets.xcassets outputs: - templateName: swift5 output: Generated/Assets.swift # Missing publicAccess!
✅ Add publicAccess for shared code
xcassets: inputs: Resources/Assets.xcassets outputs: - templateName: swift5 output: Generated/Assets.swift params: publicAccess: true # Accessible from other modules
Generated Code Usage
NEVER use string literals alongside SwiftGen:
// ❌ Defeats the purpose let icon = UIImage(named: "home") // String literal! let title = NSLocalizedString("auth.login.title", comment: "") // String literal!
// ✅ Use generated constants everywhere let icon = Asset.Icons.home.image let title = L10n.Auth.Login.title
NEVER modify generated files:
// ❌ Changes will be overwritten // Generated/Assets.swift enum Asset { enum Icons { static let home = ImageAsset(name: "home")
// My custom addition <- WILL BE DELETED ON NEXT RUN
static let customIcon = ImageAsset(name: "custom")
}
}
// ✅ Extend in separate file // Extensions/Asset+Custom.swift extension Asset.Icons { // Extensions survive regeneration }
Build Phase
NEVER put SwiftGen after Compile Sources:
❌ Generated files don't exist when compiling
Build Phases order:
- Compile Sources <- Fails: Assets.swift doesn't exist!
- Run Script (SwiftGen)
✅ Generate before compiling
Build Phases order:
- Run Script (SwiftGen) <- Generates Assets.swift
- Compile Sources <- Now Assets.swift exists
NEVER skip SwiftGen availability check:
❌ Build fails if SwiftGen not installed
swiftgen config run # Error: command not found
✅ Check availability, warn instead of fail
if which swiftgen >/dev/null; then swiftgen config run --config "$SRCROOT/swiftgen.yml" else echo "warning: SwiftGen not installed, skipping code generation" fi
Version Control
NEVER commit generated files without good reason:
❌ Merge conflicts, stale files
git add Generated/Assets.swift git add Generated/Strings.swift
✅ Gitignore generated files
.gitignore
Generated/ *.generated.swift
Exception: If CI doesn't run SwiftGen, commit generated files
But then add pre-commit hook to keep them fresh
NEVER leave swiftgen.yml uncommitted:
❌ Team members can't regenerate
.gitignore swiftgen.yml <- WRONG!
✅ Commit configuration
git add swiftgen.yml git add Resources/ # Source assets
String Keys
NEVER use inconsistent key conventions:
❌ Mixed conventions — confusing
"LoginTitle" = "Log In"; "login.button" = "Sign In"; "AUTH_ERROR" = "Error";
✅ Consistent hierarchical keys
"auth.login.title" = "Log In"; "auth.login.button" = "Sign In"; "auth.error.generic" = "Error";
Essential Patterns
Complete swiftgen.yml
swiftgen.yml
Strings (Localization)
strings: inputs: - Resources/en.lproj/Localizable.strings outputs: - templateName: structured-swift5 output: Generated/Strings.swift params: publicAccess: true enumName: L10n
Assets (Images)
xcassets:
- inputs:
- Resources/Assets.xcassets outputs:
- templateName: swift5 output: Generated/Assets.swift params: publicAccess: true
Colors
colors:
- inputs:
- Resources/Colors.xcassets outputs:
- templateName: swift5 output: Generated/Colors.swift params: publicAccess: true enumName: ColorAsset
Fonts
fonts:
- inputs:
- Resources/Fonts/ outputs:
- templateName: swift5 output: Generated/Fonts.swift params: publicAccess: true
SwiftUI Convenience Extensions
// Extensions/SwiftGen+SwiftUI.swift
import SwiftUI
// Image extension extension Image { init(asset: ImageAsset) { self.init(asset.name, bundle: BundleToken.bundle) } }
// Color extension extension Color { init(asset: ColorAsset) { self.init(asset.name, bundle: BundleToken.bundle) } }
// Font extension extension Font { static func custom(_ fontConvertible: FontConvertible, size: CGFloat) -> Font { fontConvertible.swiftUIFont(size: size) } }
// Usage struct ContentView: View { var body: some View { VStack { Image(asset: Asset.Icons.home) .foregroundColor(Color(asset: Asset.Colors.primary))
Text(L10n.Home.title)
.font(.custom(FontFamily.Roboto.bold, size: 24))
}
}
}
Build Phase Script
#!/bin/bash
Xcode Build Phase: Run Script
Move BEFORE "Compile Sources"
set -e
Check if SwiftGen is installed
if ! which swiftgen >/dev/null; then echo "warning: SwiftGen not installed. Install via: brew install swiftgen" exit 0 fi
Navigate to project root
cd "$SRCROOT"
Create output directory if needed
mkdir -p Generated
Run SwiftGen
echo "Running SwiftGen..." swiftgen config run --config swiftgen.yml
echo "SwiftGen completed successfully"
Input Files (for incremental builds):
$(SRCROOT)/swiftgen.yml $(SRCROOT)/Resources/Assets.xcassets $(SRCROOT)/Resources/en.lproj/Localizable.strings $(SRCROOT)/Resources/Colors.xcassets $(SRCROOT)/Resources/Fonts
Output Files:
$(SRCROOT)/Generated/Assets.swift $(SRCROOT)/Generated/Strings.swift $(SRCROOT)/Generated/Colors.swift $(SRCROOT)/Generated/Fonts.swift
Multi-Module Setup
Module: DesignSystem/swiftgen.yml
xcassets:
- inputs:
- Sources/DesignSystem/Resources/Colors.xcassets outputs:
- templateName: swift5 output: Sources/DesignSystem/Generated/Colors.swift params: publicAccess: true # Must be public for cross-module
Module: Feature/swiftgen.yml
strings:
- inputs:
- Sources/Feature/Resources/en.lproj/Feature.strings outputs:
- templateName: structured-swift5 output: Sources/Feature/Generated/Strings.swift params: publicAccess: false # Internal to module enumName: Strings
Quick Reference
Template Options
Asset Type Template Output
Images swift5 Asset.Category.name.image
Colors swift5 Asset.Colors.name.color
Strings structured-swift5 L10n.Category.Subcategory.key
Strings (flat) flat-swift5 L10n.keyName
Fonts swift5 FontFamily.Name.weight.font(size:)
Storyboards scenes-swift5 StoryboardScene.Name.viewController
Common Parameters
Parameter Purpose Example
publicAccess Public access level true for shared modules
enumName Custom enum name L10n, Asset, Colors
allValues Include allValues array true for debugging
preservePath Keep folder structure true for fonts
File Structure
Project/ ├── swiftgen.yml # Configuration (commit) ├── Resources/ │ ├── Assets.xcassets # Images (commit) │ ├── Colors.xcassets # Colors (commit) │ ├── Fonts/ # Custom fonts (commit) │ └── en.lproj/ │ └── Localizable.strings # Strings (commit) └── Generated/ # Output (gitignore) ├── Assets.swift ├── Colors.swift ├── Fonts.swift └── Strings.swift
Troubleshooting
Issue Cause Fix
"No such module" Generated before adding to target Add to target membership
Build fails Run Script after Compile Move before Compile Sources
Stale generated code Missing input/output files Specify all inputs/outputs
Wrong bundle Multi-target project Use correct BundleToken
Red Flags
Smell Problem Fix
String literals for assets Bypasses type safety Use generated constants
Modified generated files Changes get overwritten Use extensions instead
Run Script after Compile Files don't exist Move before Compile Sources
No availability check Build fails without SwiftGen Add which swiftgen check
Committed generated files Merge conflicts, staleness Gitignore, generate on build
Missing publicAccess Can't use across modules Add publicAccess: true
Mixed key conventions Inconsistent L10n structure Use hierarchical keys