Agent skill

ios-workflow

iOS-specific workflow activated automatically when platform-context.json reports primary_platform=ios. Handles toolchain verification, Swift Testing patterns, XcodeBuildMCP integration, and SwiftLint.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/ios-workflow

SKILL.md

iOS Workflow

Activated automatically when the Platform Detection Engine identifies an iOS/Swift project. Never activate this manually in non-iOS projects.

Phase 1: Toolchain verification

When iOS is detected, check and report tool availability:

๐ŸŽ iOS project detected. Checking toolchain...

โœ… swift        โ€” found (Swift 6.0.3)
โœ… xcodebuild   โ€” found (Xcode 16.2)
โœ… swiftlint    โ€” found (0.57.0)
โœ… XcodeBuildMCP โ€” found (~/.claude/skills/xcodebuildmcp/SKILL.md)
โš ๏ธ xcbeautify  โ€” not found (optional: brew install xcbeautify)

iOS toolchain ready.

Checks to perform:

  • swift --version โ†’ report Swift version
  • xcodebuild -version โ†’ report Xcode version
  • command -v swiftlint && swiftlint version โ†’ report version or "not found"
  • ls ~/.claude/skills/xcodebuildmcp/SKILL.md 2>/dev/null โ†’ XcodeBuildMCP presence
  • command -v xcbeautify โ†’ optional, just report

None of these checks are blockers โ€” missing tools generate warnings, not errors.

Phase 2: Test Framework Detection

Determine whether the project uses Swift Testing or XCTest:

bash
# Count test files using each framework
swift_testing_count=$(grep -r "import Testing" . --include="*.swift" -l 2>/dev/null | wc -l)
xctest_count=$(grep -r "import XCTest" . --include="*.swift" -l 2>/dev/null | wc -l)
  • swift_testing_count > 0 AND xctest_count == 0 โ†’ use Swift Testing
  • xctest_count > 0 AND swift_testing_count == 0 โ†’ use XCTest (legacy)
  • Both present โ†’ use Swift Testing for new tests, keep existing XCTest tests
  • None found โ†’ default to Swift Testing (modern standard)

Report to user:

๐Ÿงช Test framework: Swift Testing (@Test, @Suite, #expect)

or:

๐Ÿงช Test framework: XCTest (existing project uses XCTest โ€” maintaining consistency)

Phase 3: iOS Test Pipeline

Execute in this exact order:

Step 1 โ€” Unit tests (swift test or xcodebuild)

bash
# Option A: Swift Package Manager project
swift test --parallel

# Option B: Xcode project/workspace (if .xcodeproj/.xcworkspace found)
xcodebuild test \
  -scheme {detected_scheme} \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath .claude/feature-state/{slug}/test-results.xcresult \
  | xcbeautify 2>/dev/null || cat

Detect which to use: if Package.swift exists without .xcodeproj โ†’ use swift test.

Step 2 โ€” SwiftLint (files modified by this feature only)

Run SwiftLint only on modified files โ€” not the whole project:

bash
# Get files modified since the feature branch started
modified_files=$(git diff --name-only HEAD~1 -- "*.swift" 2>/dev/null)

# Run swiftlint on those files only (if swiftlint is available)
if command -v swiftlint > /dev/null 2>&1 && [ -n "$modified_files" ]; then
  swiftlint lint $modified_files --reporter json > .claude/feature-state/{slug}/swiftlint.json
  swiftlint lint $modified_files  # human-readable output
fi

If swiftlint not found: skip with โš ๏ธ swiftlint not found โ€” skipping lint.

Step 3 โ€” XcodeBuildMCP build + simulator (optional, non-blocking)

Only if capabilities.xcodebuildmcp_available == true in platform-context.json:

1. /xcodebuildmcp discover_projs
   โ†’ find .xcodeproj or .xcworkspace

2. /xcodebuildmcp session_set_defaults
   โ†’ configure scheme, destination, configuration

3. /xcodebuildmcp build_run_sim
   โ†’ build + launch on iOS Simulator
   โ†’ capture build output and simulator status

Result reporting:

โœ… App running on iPhone 16 Simulator (iOS 18.3)

or:

โš ๏ธ Simulator build failed: [error summary] โ€” tests passed, continuing to commit

XcodeBuildMCP failure is non-blocking. Unit tests passing is sufficient for the workflow.

Swift Testing patterns for iOS

When generating new tests (Test Only mode or per-task test generation), always use Swift Testing โ€” never XCTest โ€” unless the project exclusively uses XCTest.

Required imports and structure

swift
import Testing
@testable import {ModuleName}

@Suite("ViewModel or UseCase Name")
struct PersonalTrainerViewModelTests {

    @Test("describe the behavior, not the method name")
    func loadsTrainerOnAppear() async throws {
        // Arrange
        let viewModel = PersonalTrainerViewModel(
            discoverTrainersUseCase: MockDiscoverTrainersUseCase(),
            featureFlagChecker: MockFeatureFlagChecker(enabled: true)
        )

        // Act
        await viewModel.onAppear()

        // Assert
        #expect(viewModel.isFeatureEnabled == true)
        #expect(viewModel.currentTrainer != nil)
    }

    @Test("does not load trainer when feature flag is disabled")
    func doesNotLoadWhenDisabled() async {
        let viewModel = PersonalTrainerViewModel(
            featureFlagChecker: MockFeatureFlagChecker(enabled: false)
        )
        await viewModel.onAppear()
        #expect(viewModel.currentTrainer == nil)
    }

    // Parameterized test
    @Test("requestConnection returns true for valid trainer IDs",
          arguments: ["trainer-alpha", "trainer-beta", "trainer-gamma"])
    func requestConnectionSucceeds(trainerId: String) async {
        let viewModel = PersonalTrainerViewModel(
            connectTrainerUseCase: MockConnectTrainerUseCase(shouldSucceed: true)
        )
        let result = await viewModel.requestConnection(trainerId: trainerId)
        #expect(result == true)
    }
}

Rules for iOS tests

  • Use @Suite to group related tests (one suite per ViewModel/UseCase/Repository)
  • Use @Test("behavior description") โ€” describe what it does, not the method
  • Use #expect(condition) for assertions โ€” never XCTAssert
  • Use #require(value) to unwrap optionals that must exist
  • Use throws for tests that can throw, async throws for async tests
  • Use arguments: for data-driven tests (replaces XCTest parametrize)
  • Use .tags() trait for categorization: @Test("...", .tags(.unit, .viewModel))
  • Never use XCTest, XCTAssert*, or class FooTests: XCTestCase

File structure for iOS tests

Follow the existing project convention. If no convention exists, use:

{ProjectName}/
โ””โ”€โ”€ {ProjectName}Tests/
    โ”œโ”€โ”€ Domain/
    โ”‚   โ””โ”€โ”€ UseCases/
    โ”‚       โ””โ”€โ”€ {UseCase}Tests.swift     โ† one file per use case
    โ”œโ”€โ”€ Data/
    โ”‚   โ”œโ”€โ”€ Repositories/
    โ”‚   โ”‚   โ””โ”€โ”€ {Repository}Tests.swift
    โ”‚   โ””โ”€โ”€ Services/
    โ”‚       โ””โ”€โ”€ {Service}Tests.swift
    โ””โ”€โ”€ Presentation/
        โ””โ”€โ”€ Features/
            โ””โ”€โ”€ {Feature}/
                โ””โ”€โ”€ {Feature}ViewModelTests.swift

Test file naming

  • One test file per production file: PersonalTrainerViewModel.swift โ†’ PersonalTrainerViewModelTests.swift
  • Place in mirror directory under {ProjectName}Tests/

Test Only mode โ€” iOS

When --mode test-only is invoked on an iOS project:

  1. Detect test framework (Swift Testing vs XCTest)
  2. Find Swift files without corresponding test files:
    bash
    find . -name "*.swift" \
      ! -name "*Tests.swift" \
      ! -name "*Mock*" \
      ! -path "*/Tests/*" \
      -type f 2>/dev/null
    
  3. For each file without tests, check if it's testable (ViewModels, UseCases, Repositories, Services)
  4. Present to user:
    Found 3 files without test coverage:
    - Presentation/Features/PersonalTrainer/PersonalTrainerViewModel.swift
    - Domain/UseCases/ConnectTrainerUseCase.swift
    - Data/Repositories/TrainerRepository.swift
    
    Generate Swift Testing tests for these files? [yes/no/select]
    
  5. Generate tests following Swift Testing patterns above
  6. Run swift test --parallel
  7. If XcodeBuildMCP available: optionally run build on simulator
  8. Report coverage delta

Configuration in platform-context.json

The iOS entry in platform-context.json includes:

json
{
  "type": "ios",
  "test_framework": "swift-testing",
  "capabilities": {
    "swiftlint_available": true,
    "xcodebuildmcp_available": true,
    "swift_testing_available": true
  }
}

This configuration is ignored completely on non-iOS projects.

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results