Agent Skills: SwiftGen Integration — Expert Decisions

Expert SwiftGen decisions for iOS/tvOS: when type-safe assets add value, template selection trade-offs, organization strategies, and build phase configuration. Use when setting up SwiftGen, choosing templates, or debugging generation issues. Trigger keywords: SwiftGen, type-safe, Asset, L10n, ImageAsset, ColorAsset, FontFamily, swiftgen.yml, structured-swift5, code generation, asset catalog

UncategorizedID: kaakati/rails-enterprise-dev/swiftgen-integration

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Kaakati/rails-enterprise-dev/tree/HEAD/plugins/reactree-ios-dev/skills/swiftgen-integration

Skill Files

Browse the full folder contents for swiftgen-integration.

Download Skill

Loading file tree…

plugins/reactree-ios-dev/skills/swiftgen-integration/SKILL.md

Skill Metadata

Name
swiftgen-integration
Description
"Expert SwiftGen decisions for iOS/tvOS: when type-safe assets add value, template selection trade-offs, organization strategies, and build phase configuration. Use when setting up SwiftGen, choosing templates, or debugging generation issues. Trigger keywords: SwiftGen, type-safe, Asset, L10n, ImageAsset, ColorAsset, FontFamily, swiftgen.yml, structured-swift5, code generation, asset catalog"

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:
1. Compile Sources  <- Fails: Assets.swift doesn't exist!
2. Run Script (SwiftGen)

# ✅ Generate before compiling
Build Phases order:
1. Run Script (SwiftGen)  <- Generates Assets.swift
2. 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 |