blake@xcode:~/Projects$ cat ios-agent-development.md

Building iOS Apps with AI Agents: The Practitioner's Guide

# Ship iOS apps faster with AI coding agents. Claude Code, Codex CLI, and Xcode 26.3 native agents — MCP setup, CLAUDE.md patterns, hooks, and honest lessons from 8 production apps.

words: 17219 read_time: 86m updated: 2026-04-13 00:00
$ less ios-agent-development.md

TL;DR: Three agent runtimes now ship code for iOS: Claude Code CLI with MCP, Codex CLI with MCP, and Xcode 26.3’s native Intelligence agents. Two MCP servers (XcodeBuildMCP with 59 tools and Apple’s xcrun mcpbridge with 20 tools) give agents structured access to builds, tests, simulators, and debugging. This guide covers real CLAUDE.md patterns, hook configurations, and honest assessments of what works and what breaks — drawn from 8 production iOS apps totaling 293 Swift files. Agents excel at SwiftUI views, SwiftData models, refactoring, and build error diagnosis. They fail at .pbxproj modifications, code signing, and visual debugging. The gap between “agent writes Swift” and “agent ships an iOS app” is bridged by configuration, not by prompting.

I build 8 iOS apps with AI coding agents. Not prototypes — apps in the App Store, with HealthKit integrations, Metal shaders, SpriteKit physics, iCloud sync, Live Activities, Game Center leaderboards, and multi-platform targets spanning iOS, watchOS, and tvOS. Every line of Swift in these apps was either written by an agent and reviewed by me, or written by me and refactored by an agent. By my estimate, the ratio is roughly 85/15 in the agent’s favor.

This guide is the reference I wish existed when I started. It covers the full stack: which agent runtime to use, how to configure MCP servers for structured build access, what to put in your CLAUDE.md, which hooks prevent the agent from destroying your Xcode project, and — critically — where agents fail and you need to take the wheel.

Key Takeaways

For iOS developers new to AI agents:

  • Start with Claude Code CLI + XcodeBuildMCP. It is the most mature runtime with the deepest MCP tool coverage. Install two commands, add a CLAUDE.md to your project, and the agent can build, test, and debug without you copying error messages.
  • Never let an agent modify .pbxproj. This is the single most important rule. A PreToolUse hook that blocks writes to .pbxproj and .xcodeproj/ will save you hours of recovery.
  • Your CLAUDE.md is the agent’s onboarding document. Every hour spent on CLAUDE.md saves ten hours of correcting agent mistakes.

For experienced agent users adding iOS to their workflow:

  • MCP transforms the iOS build loop. Before MCP, agents wrote Swift but could not verify it compiled. With XcodeBuildMCP, the agent writes code, builds it, reads structured errors, fixes them, and runs tests — autonomously.
  • Three runtimes serve different needs. Claude Code CLI for deep agentic sessions, Codex CLI for headless batch work, Xcode 26.3 native agents for quick inline fixes without leaving the IDE.
  • Hook infrastructure carries over. Your existing PostToolUse formatters, PreToolUse blockers, and test runner hooks work identically for iOS projects with minor path adjustments.

For team leads evaluating AI-assisted iOS development:

  • Agent effectiveness scales with project documentation, not project size. A 63-file app with a detailed CLAUDE.md produces better agent output than a 14-file app with none.
  • The .pbxproj boundary is non-negotiable. Agents cannot reliably edit Xcode project files. Your workflow must account for manual file addition to Xcode targets.
  • Honest ROI: agents handle 70-80% of implementation work. The remaining 20-30% — visual polish, signing, performance tuning, App Store submission — requires human judgment.

Choose Your Path

What you need Go here
Set up MCP for the first time MCP Setup: The Complete Configuration — install both servers, verify, configure agents
Write a CLAUDE.md for your iOS project CLAUDE.md Patterns for iOS Projects — real examples from 8 apps
Compare the three agent runtimes Three Agent Runtimes for iOS — Claude Code vs. Codex vs. Xcode native
Understand what agents can and cannot do What Agents Do Well and What Agents Do Poorly
Set up hooks for iOS development Hooks for iOS Development — format-on-save, .pbxproj protection, test runners
Deep reference (this page) Keep reading — everything from setup to advanced patterns

How to Use This Guide

This is a 3,000+ line reference. Start where your experience level fits:

Experience Start Here Then Explore
New to iOS + agents PrerequisitesMCP SetupYour First Agent Session CLAUDE.md Patterns, What Works/Doesn’t
iOS developer, new to agents Three RuntimesMCP SetupCLAUDE.md Hooks, Architecture Patterns
Agent user, new to iOS Architecture PatternsWhat Agents Do PoorlyCLAUDE.md Framework-Specific Context, Advanced Workflows
Experienced with both Advanced WorkflowsHooksMulti-Platform Patterns Runtime Comparison, The Portfolio

Table of Contents

  1. The Portfolio: 8 Apps, 293 Files
  2. Prerequisites
  3. Three Agent Runtimes for iOS
  4. MCP Setup: The Complete Configuration
  5. CLAUDE.md Patterns for iOS Projects
  6. Your First Agent Session
  7. What Agents Do Well in iOS
  8. What Agents Do Poorly in iOS
  9. Hooks for iOS Development
  10. Architecture Patterns That Work with Agents
  11. Framework-Specific Context
  12. Multi-Platform Patterns
  13. Advanced Workflows
  14. Real-World Case Studies
  15. Project Lifecycle with Agents
  16. Configuring Agent Definitions
  17. Testing Patterns for Agent-Assisted iOS
  18. Context Window Management for iOS Projects
  19. Troubleshooting
  20. Common Agent Mistakes in iOS
  21. The Honest Assessment
  22. FAQ
  23. Quick Reference Card
  24. References
Topic Resource
MCP setup for Xcode (shorter blog post) Two MCP Servers Made Claude Code an iOS Build System
Claude Code CLI complete reference Claude Code CLI: The Complete Guide
Codex CLI reference Codex CLI: The Complete Guide
Hook system deep dive Anatomy of a Claw: 84 Hooks as an Orchestration Layer
Agent architecture patterns Agent Architecture Guide

The Portfolio: 8 Apps, 293 Files

Before diving into configuration, here is what this guide is drawn from. These are not toy projects — they span five Apple frameworks, three platforms, and the full range of iOS complexity from a 14-file workout tracker to a 63-file multi-platform meditation timer.

App Stack Files Complexity
Banana List SwiftUI + SwiftData + iCloud Drive sync + MCP server for Claude Desktop 53 Full CRUD, iCloud sync, custom MCP server that exposes the app’s data to Claude Desktop
Ace Citizenship SwiftUI study app + FastAPI backend 26 Client-server, REST API integration, quiz engine
TappyColor SpriteKit color matching game 30 Game loop, physics, touch handling, particle effects
Return Zen meditation timer — iOS 26+, watchOS, tvOS 63 HealthKit, Live Activities, Watch extended runtime, TV focus navigation, iCloud session sync
amp97 Metal shaders + audio visualization 41 Custom Metal render pipeline, audio analysis, real-time GPU compute
Reps SwiftUI + SwiftData workout tracking 14 Minimal viable app, clean SwiftData patterns
Water SwiftUI + SwiftData + Metal + HealthKit hydration tracking 34 Metal fluid simulation, HealthKit water intake logging, widget
Starfield Destroyer SpriteKit + Metal space shooter 32 99 levels, 8 ships, Game Center leaderboards, Metal post-processing

Why the file counts matter: Agent effectiveness correlates with project legibility, not project size. Return (63 files) produces better agent output than amp97 (41 files) because Return has a detailed CLAUDE.md with file annotations, architecture diagrams, and explicit patterns. amp97’s Metal shaders are inherently harder for agents to reason about regardless of documentation quality.


Prerequisites

Before setting up any agent runtime for iOS development:

Required: - macOS 15+ (Sequoia) or macOS Tahoe - Xcode 26.3+ installed and configured (accepted license agreement, downloaded platforms) - At least one iOS Simulator runtime installed - An Anthropic API account (for Claude Code) or OpenAI account (for Codex)

Recommended: - SwiftFormat installed (brew install swiftformat) — used by format-on-save hooks - SwiftLint installed (brew install swiftlint) — optional but useful for style enforcement - Familiarity with the terminal — all three runtimes operate from or integrate with the command line

Verify your Xcode installation:

# Check Xcode version
xcodebuild -version
# Expected: Xcode 26.3 or later

# Check available simulators
xcrun simctl list devices available
# Expected: at least one iPhone simulator

# Verify xcrun mcpbridge is available
xcrun mcpbridge --help
# Expected: usage information (not "command not found")

If xcrun mcpbridge returns “command not found,” you need Xcode 26.3 or later. Install or update Xcode through the App Store or developer.apple.com. Note: xcode-select --install only installs Command Line Tools, which do not include mcpbridge — you need the full Xcode.app.


Three Agent Runtimes for iOS

Three distinct runtimes can write, build, and test iOS code. They are not interchangeable — each has different strengths, different MCP integration patterns, and different ideal use cases.

1. Claude Code CLI

What it is: Anthropic’s terminal-based agentic coding assistant. Reads your codebase, executes commands, modifies files, and connects to external tools via MCP.

MCP integration: Full support for both XcodeBuildMCP and Apple’s Xcode MCP. The agent discovers tools via MCP protocol and calls them with structured parameters. 59 + 20 tools across both servers.

Setup:

# Install Claude Code (if not already installed)
claude --version  # verify installation

# Add XcodeBuildMCP (59 tools — builds, tests, simulators, debugging)
claude mcp add XcodeBuildMCP \
  -s user \
  -e XCODEBUILDMCP_SENTRY_DISABLED=true \
  -- npx -y xcodebuildmcp@latest mcp

# Add Apple Xcode MCP (20 tools — file ops, diagnostics, Swift REPL, previews)
claude mcp add --transport stdio xcode \
  -s user -- xcrun mcpbridge

Best for: Deep implementation sessions — building new features, refactoring across multiple files, debugging complex issues, running build-test-fix loops autonomously. Claude Code’s 1M context window (with Opus 4.6) means the agent can hold most small-to-medium iOS projects in working memory — in my experience, up to roughly 50 files depending on file size.

Typical session:

You: Add a new SettingsView with toggles for notifications and haptic feedback.
     Use the existing Settings.swift pattern from TimerManager.
     Build for simulator after implementation and fix any errors.

Claude: [reads Settings.swift, TimerManager.swift, existing views]
        [creates SettingsView.swift]
        [calls build_sim via MCP]
        [reads structured error: missing import HealthKit]
        [fixes import]
        [calls build_sim again  success, 0 errors, 1 warning]
        [calls test_sim  all tests pass]

The key difference from pre-MCP workflow: the agent never asks you to build manually or paste error output. The build-error-fix loop is autonomous.

2. Codex CLI

What it is: OpenAI’s terminal-based coding agent. Similar in concept to Claude Code but uses OpenAI models (GPT-4o, o3) and has a different permission model.

MCP integration: Codex supports MCP via the codex mcp add command. Apple’s Xcode MCP works directly:

# Add Apple Xcode MCP to Codex
codex mcp add xcode -- xcrun mcpbridge

XcodeBuildMCP also works with Codex through the same npx command:

# Add XcodeBuildMCP to Codex
codex mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp

Best for: Headless batch operations, CI/CD integration, and tasks where you want a second opinion from a different model family. Codex’s sandbox mode runs code in isolated environments, which is useful for destructive operations like test suite runs that modify state.

Key differences from Claude Code: - Uses OpenAI models instead of Claude models - Different context window sizes and token economics - Sandbox-first permission model (more restrictive by default) - Smaller MCP ecosystem (fewer community servers tested) - Hooks system available (v0.119.0+) but less mature than Claude Code’s — fewer event types and no conditional if field

When to use Codex over Claude Code for iOS:

Use Codex when you want model diversity — having a second agent review code written by the first catches different classes of errors. The collab workflow (Claude builds, Codex reviews) is effective for iOS because SwiftUI patterns that look correct to one model family may have subtle issues that another catches. Metal shaders and concurrency patterns benefit especially from dual-model review.

3. Xcode 26.3 Native Agents

What it is: Apple integrated AI coding agents directly into Xcode’s Intelligence panel. As of Xcode 26.3, you can configure Claude Agent and Codex as intelligence providers in Xcode Settings > Intelligence.

Setup:

  1. Open Xcode 26.3+
  2. Navigate to Settings > Intelligence
  3. Add a new provider:
  4. For Claude: Select “Claude Agent,” enter your Anthropic API key
  5. For Codex: Select “Codex,” enter your OpenAI API key
  6. The agent appears in the Intelligence sidebar and can be invoked inline

Best for: Quick inline edits, code completion with agent-level reasoning, and developers who prefer not to leave Xcode. The native integration means the agent has direct access to Xcode’s project context — open files, build targets, scheme configuration — without MCP bridging.

Limitations compared to CLI agents: - No hook system — you cannot enforce format-on-save or block .pbxproj writes - No CLAUDE.md loading — the agent does not read your project-level configuration files - Limited autonomy — the agent operates on the current file or selection, not across the full project - No subagent delegation — complex multi-step tasks cannot be parallelized - No MCP server configuration — the agent uses Xcode’s built-in tools only

When to use Xcode native agents:

For quick, scoped edits where switching to the terminal is overhead. “Add a computed property to this model.” “Write a unit test for this function.” “Refactor this view to use @Observable.” Tasks that touch one or two files and do not require a build-test cycle.

For anything that requires building, testing, multi-file refactoring, or autonomous error correction, use a CLI agent with MCP.

Runtime Comparison Matrix

Capability Claude Code CLI Codex CLI Xcode 26.3 Native
MCP support Full (79 tools) Full (79 tools) Built-in Xcode tools only
Hook system Yes (mature) Yes (basic, v0.119.0+) No
CLAUDE.md / project config Yes codex.md equivalent No
Autonomous build-test-fix Yes (via MCP) Yes (via MCP) Partial (inline only)
Subagent delegation Yes (up to 10 parallel) No No
Context window 1M tokens (Opus 4.6) Varies by model Varies by provider
Multi-file operations Full codebase access Full codebase access Current file / selection
.pbxproj protection Via hooks Manual N/A (uses Xcode natively)
Format-on-save Via PostToolUse hooks External tooling Xcode settings
Offline capability No No No
Cost model Anthropic API usage OpenAI API usage Provider API usage

The recommendation: Use Claude Code CLI as your primary runtime. Use Xcode 26.3 native agents for quick inline edits. Use Codex CLI for review passes and batch operations. The three complement rather than compete.


MCP Setup: The Complete Configuration

MCP (Model Context Protocol) is what transforms an agent from “writes Swift and hopes you build it” to “writes Swift, builds it, reads structured errors, and fixes them.” This section goes deeper than the blog post — covering both servers, all installation methods, verification, and the agent configuration that ensures tools are actually used.

XcodeBuildMCP: 59 Tools for Headless iOS Development

XcodeBuildMCP wraps xcodebuild, xcrun simctl, and LLDB into 59 structured MCP tools. It works without Xcode running — the entire build-test-debug cycle operates headlessly via Apple’s command-line tools.

Installation options:

# Option 1: Via npx (recommended — always uses latest version)
claude mcp add XcodeBuildMCP \
  -s user \
  -e XCODEBUILDMCP_SENTRY_DISABLED=true \
  -- npx -y xcodebuildmcp@latest mcp

# Option 2: Via Homebrew (pinned version, manual updates)
brew install xcodebuildmcp
claude mcp add XcodeBuildMCP \
  -s user \
  -e XCODEBUILDMCP_SENTRY_DISABLED=true \
  -- xcodebuildmcp mcp

# Option 3: Project-scoped (omit -s user)
claude mcp add XcodeBuildMCP \
  -e XCODEBUILDMCP_SENTRY_DISABLED=true \
  -- npx -y xcodebuildmcp@latest mcp

The -s user flag makes the server available globally across all projects. Omit it for project-scoped installation (useful if you only want MCP in iOS projects, not web projects).

The -e XCODEBUILDMCP_SENTRY_DISABLED=true environment variable disables crash report telemetry. XcodeBuildMCP includes Sentry by default, which sends error data including file paths. Opt out unless you want to contribute diagnostics to the project.1

Full tool inventory (59 tools across 8 categories):

Category Tools What They Do
Project discovery discover_projs, list_schemes, list_targets Find .xcodeproj/.xcworkspace files, list available schemes and targets
Building build_sim, build_device, build_mac Build with structured JSON error/warning output by file and line
Testing test_sim, test_device Run tests with per-method pass/fail results
Simulator lifecycle list_sims, boot_sim, shutdown_sim, open_sim, session_set_defaults Create, boot, manage, and configure simulators
Device management list_devices, install_device, launch_device Real device deployment and management
Debugging debug_attach_sim, debug_attach_device, debug_breakpoint, debug_stack, debug_variables, debug_eval, debug_continue, debug_step, debug_detach Full LLDB integration with breakpoints and variable inspection
UI automation snapshot_ui, screenshot, tap, swipe, type_text Automated interaction and visual capture
Project scaffolding create_project, add_file, add_package Create projects and add dependencies

The tools that matter most for daily work:

  1. build_sim — You will call this hundreds of times. It returns JSON with errors categorized by file, line, and severity. The agent reads the error, navigates to the file, and fixes it without you touching anything.

  2. test_sim — Returns per-test-method results. The agent knows exactly which test failed and why, not just “tests failed.”

  3. list_sims + boot_sim — Simulator management without memorizing xcrun simctl flags. The agent discovers available runtimes and picks an appropriate device.

  4. discover_projs + list_schemes — Project introspection. The agent does not need to guess your scheme name or workspace structure.

  5. debug_attach_sim + debug_stack + debug_variables — Remote LLDB debugging. The agent can set breakpoints, inspect variables, and step through code without you opening the debugger.

Apple Xcode MCP: 20 Tools Bridging into Xcode

Apple’s MCP server ships with Xcode 26.3 via xcrun mcpbridge. It communicates with a running Xcode process through XPC (Apple’s inter-process communication framework), exposing internal state that no CLI tool can access.

Installation:

# Standard installation (global)
claude mcp add --transport stdio xcode \
  -s user -- xcrun mcpbridge

# For Codex CLI
codex mcp add xcode -- xcrun mcpbridge

Requires Xcode 26.3+ and a running Xcode process. If Xcode is not open, every MCP call through this server will fail or hang. XcodeBuildMCP does not have this limitation.

Tool inventory (20 tools across 5 categories):

Category Tools What They Do
File operations XcodeRead, XcodeWrite, XcodeUpdate, XcodeGlob, XcodeGrep Read/write files within the Xcode project context
Build & test BuildProject, GetBuildLog, RunAllTests, RunSomeTests Build and test with Xcode’s internal build system
Diagnostics XcodeListNavigatorIssues, XcodeRefreshCodeIssuesInFile Real-time code diagnostics (not just build errors)
Code & documentation ExecuteSnippet, DocumentationSearch Swift REPL execution and Apple documentation search
Previews RenderPreview Headless SwiftUI preview rendering

Tools that are unique to Apple MCP (not available in XcodeBuildMCP):

  1. DocumentationSearch — Searches Apple’s developer documentation including WWDC sessions. Faster and more reliable than web search for Apple API questions. Ask “is HKQuantityType(.dietaryWater) valid?” and get a definitive answer from the source.

  2. ExecuteSnippet — Swift REPL execution within the project’s context. The agent can verify API behavior, test type conversions, and validate expressions without building the full app.

  3. RenderPreview — Renders SwiftUI previews headlessly. The agent can check whether a view renders without errors, though it cannot evaluate visual correctness (the render is returned as data, not visually inspected).

  4. XcodeListNavigatorIssues — Returns real-time diagnostics from Xcode’s analyzer, not just build errors. Catches issues like unused variables, potential retain cycles, and deprecation warnings that the build system does not surface.

Why Both Servers

They overlap on builds and tests but differ fundamentally:

┌─────────────────────────────────────────────────────────────────┐
                     MCP TOOL COVERAGE                           
├─────────────────────────────────────────────────────────────────┤
                                                                 
  XcodeBuildMCP (59 tools)        Apple Xcode MCP (20 tools)    
  ┌─────────────────────┐         ┌─────────────────────┐       
   Standalone                     Requires Xcode             
   (no Xcode process)            (XPC bridge)               
                                                            
    Simulators          BOTH     Documentation            
    Real devices       ┌─────┐   Swift REPL               
    LLDB debugging     Build   SwiftUI previews         
    UI automation      Test    Live diagnostics         
    Project scaffold   └─────┘   Analyzer issues          
    Screenshot                                             
  └─────────────────────┘         └─────────────────────┘       
                                                                 
└─────────────────────────────────────────────────────────────────┘

Use XcodeBuildMCP for: The build-test-debug cycle. It works without Xcode open, consumes less system memory, and provides richer simulator and device management. This is your primary build tool.

Use Apple Xcode MCP for: Documentation lookups, Swift REPL verification, SwiftUI preview rendering, and real-time diagnostics. Keep Xcode open during sessions that need these capabilities.

In practice: I use XcodeBuildMCP for ~90% of MCP calls and Apple Xcode MCP for documentation and REPL verification. The agent defaults to XcodeBuildMCP for builds and tests because it is faster (no Xcode process overhead) and more reliable (no XPC dependency).

Verification

After installing both servers, verify they are connected:

# List all configured MCP servers
claude mcp list

# Expected output includes:
# XcodeBuildMCP: npx -y xcodebuildmcp@latest mcp - Connected
# xcode: xcrun mcpbridge - Connected

If a server shows “Disconnected” or does not appear:

  1. XcodeBuildMCP not connecting: Ensure Node.js is installed (node --version). The npx command requires Node.js 18+.
  2. Apple Xcode MCP not connecting: Ensure Xcode 26.3+ is installed and the xcrun mcpbridge command works in your terminal. Open Xcode at least once to accept the license agreement.
  3. Both not appearing: Restart Claude Code (claude in a new terminal). MCP servers registered mid-session may not appear until restart.

Teaching the Agent to Use MCP

Installing MCP servers is necessary but insufficient. Without explicit guidance, the agent may fall back to running xcodebuild via Bash (unstructured output, wasted context tokens) or using web search for Apple documentation (slower, less reliable).

Add this to your CLAUDE.md or agent definition:

## Build & Test — Always Use MCP

Prefer MCP tools over raw shell commands for ALL build operations:

- **Build**: `build_sim` / `build_device` (NOT `xcodebuild` via Bash)
- **Test**: `test_sim` / `test_device` (NOT `xcodebuild test` via Bash)
- **Simulators**: `list_sims`, `boot_sim`, `open_sim` (NOT `xcrun simctl` via Bash)
- **Debug**: `debug_attach_sim`, `debug_stack`, `debug_variables`
- **Apple docs**: `DocumentationSearch` (NOT WebSearch for Apple APIs)
- **Swift verification**: `ExecuteSnippet` (NOT `swift` via Bash)
- **Previews**: `RenderPreview` for headless SwiftUI verification

MCP returns structured JSON. Bash returns unstructured text.
Structured data means fewer tokens consumed and better error diagnosis.

This guidance ensures the agent reaches for MCP tools first. Without it, you will observe the agent constructing long xcodebuild commands via Bash, consuming thousands of context tokens parsing the output, and sometimes misidentifying the actual error.


CLAUDE.md Patterns for iOS Projects

Your CLAUDE.md is the most important file in the project for agent-assisted development. It is the agent’s onboarding document — the difference between a new hire who read the architecture docs and one who is guessing.

Every iOS project I maintain has a CLAUDE.md. Here are the patterns that work, drawn from all 8 apps.

The Essential Sections

Every iOS CLAUDE.md needs these six sections. Everything else is optional.

1. Project Identity

# Return - Zen Focus Timer

**Bundle ID:** `com.941apps.Return`
**Target:** iOS 26+ / macOS Tahoe / watchOS 26+ / tvOS 26+
**Architecture:** SwiftUI with @Observable pattern, companion Watch and TV apps
**Swift version:** 6.2
**Minimum deployment:** iOS 26.0

Why this matters: The agent needs to know the deployment target before writing any code. An agent targeting iOS 17 will use NavigationView and @ObservedObject. An agent targeting iOS 26 will use NavigationStack and @Observable. The bundle ID matters for entitlements and HealthKit configuration. The Swift version determines concurrency model (async/await vs. completion handlers, strict concurrency vs. lenient).

2. File Structure with Purpose Annotations

## File Structure

Return/ ├── ReturnApp.swift # App entry, dark mode enforcement ├── ContentView.swift # Main timer view with theme backgrounds ├── TimerManager.swift # Timer state, logic, and repeat handling ├── AudioManager.swift # Sound playback with AVAudioPlayer ├── Settings.swift # Centralized settings with validation ├── SettingsSheet.swift # Settings UI ├── HealthKitManager.swift # Mindful session logging + cross-device sync ├── LiveActivityManager.swift # Lock Screen/Dynamic Island ├── Theme.swift # Theme definitions ├── ThemeManager.swift # Theme state management ├── VideoBackgroundView.swift # AVPlayer video backgrounds ├── GlassTextShape.swift # Core Text glyph paths for glass effect ├── GlassTimerText.swift # Timer text with glass material └── Constants.swift # App constants


The inline comments after each filename are not decoration. They are the single highest-leverage documentation you can write. When the agent is deciding where to add a new feature, these annotations guide it to the correct file on the first attempt instead of reading every file to understand the project layout.

Anti-pattern: Listing files without annotations. TimerManager.swift tells the agent nothing about whether it handles state, UI, or both. TimerManager.swift # Timer state, logic, and repeat handling tells it exactly what belongs there and what does not.

3. Build and Test Commands

## Build & Test

Build for iOS simulator:
```bash
xcodebuild -scheme Return -destination 'platform=iOS Simulator,name=iPhone 16 Pro' build

Run tests:

xcodebuild -scheme Return -destination 'platform=iOS Simulator,name=iPhone 16 Pro' test

Run tvOS tests:

xcodebuild -scheme ReturnTV -destination 'platform=tvOS Simulator,name=Apple TV' test

Prefer MCP tools (build_sim, test_sim) over these raw commands. MCP returns structured JSON with categorized errors.

Include the raw commands even though the agent should prefer MCP. The raw commands serve as fallback documentation and make the scheme names and destinations explicit.

#### 4. Key Patterns and Rules

```markdown
## Key Patterns

### Observable Architecture
- ALL view models use `@Observable` (NEVER `ObservableObject`)
- ALL navigation uses `NavigationStack` (NEVER `NavigationView`)
- State management via `@Observable` classes with `@MainActor` isolation

### Settings Pattern
- Centralized `Settings.shared` singleton
- All settings bounded to valid ranges with validation
- Sound names validated against whitelist
- Thread-safe access via @MainActor

### Audio System
- `AVAudioPlayer` with `.playback` category (plays in silent mode)
- Silent audio loop for background execution
- Bell playback with completion callbacks and token-based staleness

These patterns prevent the agent from introducing inconsistencies. Without explicit pattern documentation, the agent will sometimes use ObservableObject in one file and @Observable in another, or create a new settings mechanism instead of using the existing Settings.shared singleton.

5. Things the Agent Must Never Do

## Rules

- **NEVER modify .pbxproj files** — create Swift files, then I will add them to Xcode manually
- **NEVER modify .xcodeproj/ contents directly**
- **NEVER add new package dependencies** without asking first
- **NEVER change the deployment target**
- **NEVER modify entitlements files** unless explicitly asked
- **NEVER use NavigationView** — always NavigationStack
- **NEVER use ObservableObject** — always @Observable
- **NEVER use @StateObject** — always @State with @Observable

Explicit prohibitions are more effective than implicit expectations. The agent follows negative constraints more reliably than positive suggestions because they are binary (do it / do not do it) rather than heuristic (prefer this / sometimes use that).

6. Framework-Specific Context

This section varies by app. Include it for any framework that has non-obvious configuration:

For HealthKit apps:

## HealthKit Configuration

- Entitlement: `com.apple.developer.healthkit`
- Info.plist keys:
  - `NSHealthShareUsageDescription`: "Return reads your mindful minutes..."
  - `NSHealthUpdateUsageDescription`: "Return logs meditation sessions..."
- Category types: `HKCategoryType(.mindfulSession)`
- Authorization checked on every write (user can revoke at any time)
- HealthKit is unavailable on tvOS — guard with `#if canImport(HealthKit)`

For SwiftData apps:

## SwiftData Models

### Model Relationships
- `GroceryList` has many `GroceryItem` (cascade delete)
- `GroceryItem` belongs to one `GroceryList`
- `GroceryItem` has optional `Category`

### Model Container Setup
- Configured in App struct with `modelContainer(for:)`
- Schema versioning: currently V2
- Migration plan: `GroceryMigrationPlan` handles V1 → V2

### Queries
- `@Query(sort: \GroceryItem.name)` for sorted fetches
- `@Query(filter: #Predicate { !$0.isCompleted })` for active items
- Always use `@Query` in views, `modelContext.fetch()` in managers

For SpriteKit apps:

## SpriteKit Scene Hierarchy

GameScene (SKScene) ├── backgroundLayer (SKNode, zPosition: -100) │ └── StarfieldNode (custom, parallax scrolling) ├── gameLayer (SKNode, zPosition: 0) │ ├── playerShip (PlayerNode, zPosition: 10) │ ├── enemyContainer (SKNode, zPosition: 5) │ └── bulletPool (SKNode, zPosition: 8) ├── effectsLayer (SKNode, zPosition: 50) │ └── ParticleManager (manages explosion/trail emitters) └── hudLayer (SKNode, zPosition: 100) ├── scoreLabel (SKLabelNode) └── healthBar (HealthBarNode)

- Physics categories defined in `PhysicsCategory.swift` as bitmasks
- Contact detection via `didBegin(_ contact:)` on GameScene
- Bullet pooling: pre-allocate 50, recycle via `removeFromParent()` + re-add

For Metal apps:

## Metal Pipeline

- Render pipeline: `MetalView``Renderer``ShaderLibrary`
- Compute pipeline: `AudioAnalyzer` → compute shader → texture output
- Shared uniforms struct: `Uniforms` in `ShaderTypes.h` (bridged to Swift)
- Frame timing: `CADisplayLink` drives render loop
- Buffer triple-buffering: 3 in-flight frames with semaphore

### Shader Files
- `Shaders.metal` — Main render shaders (vertex + fragment)
- `Compute.metal` — Audio analysis compute kernel
- `PostProcess.metal` — Bloom and color grading

### DO NOT modify Metal shaders without testing on device.
Simulator Metal is not representative of device GPU behavior.

Real CLAUDE.md: Banana List (SwiftUI + SwiftData + iCloud + MCP Server)

Here is an annotated example showing how all six sections work together for a moderately complex app. This is the CLAUDE.md pattern I use for Banana List, a 53-file grocery list app with iCloud sync and a custom MCP server that exposes the app’s data to Claude Desktop:

# Banana List - Grocery List App

**Bundle ID:** `com.941apps.BananaList`
**Target:** iOS 26+
**Architecture:** SwiftUI + SwiftData + iCloud Drive sync
**Swift version:** 6.2
**Minimum deployment:** iOS 26.0

## Core Features

- Grocery lists with items, categories, and quantities
- iCloud Drive sync via SwiftData CloudKit integration
- Custom MCP server exposing list data to Claude Desktop
- Liquid Glass design system
- Haptic feedback on interactions
- Share sheets for list sharing

## File Structure

BananaList/ ├── BananaListApp.swift # App entry, model container setup ├── Models/ │ ├── GroceryList.swift # @Model: list with name, items, color │ ├── GroceryItem.swift # @Model: item with name, quantity, category, isCompleted │ ├── Category.swift # @Model: user-defined categories │ └── SampleData.swift # Preview and test data ├── Views/ │ ├── ListsView.swift # Main list of grocery lists │ ├── ListDetailView.swift # Items within a list │ ├── ItemRow.swift # Single item row with swipe actions │ ├── AddItemSheet.swift # New item form │ ├── CategoryPicker.swift # Category selection with create-new │ └── SettingsView.swift # App settings ├── Managers/ │ ├── CloudSyncManager.swift # iCloud Drive sync status and conflict resolution │ └── HapticManager.swift # UIImpactFeedbackGenerator wrapper ├── MCP/ │ ├── MCPServer.swift # MCP server for Claude Desktop integration │ ├── ListTools.swift # MCP tools: list CRUD operations │ └── ItemTools.swift # MCP tools: item CRUD operations └── Extensions/ ├── Color+Extensions.swift # Custom color definitions └── View+Extensions.swift # Reusable view modifiers

## SwiftData Models

### Relationships
- `GroceryList` has many `GroceryItem` (cascade delete)
- `GroceryItem` belongs to one `GroceryList` (required)
- `GroceryItem` has optional `Category`
- `Category` has many `GroceryItem` (nullify on delete)

### Container Setup
```swift
@main
struct BananaListApp: App {
    var body: some Scene {
        WindowGroup {
            ListsView()
        }
        .modelContainer(for: [GroceryList.self, GroceryItem.self, Category.self])
    }
}

Query Patterns

  • Lists: @Query(sort: \GroceryList.name) var lists: [GroceryList]
  • Active items: @Query(filter: #Predicate { !$0.isCompleted })
  • By category: filter in-memory after fetch (SwiftData predicate limitations)

Build & Test

xcodebuild -scheme BananaList -destination 'platform=iOS Simulator,name=iPhone 16 Pro' build
xcodebuild -scheme BananaList -destination 'platform=iOS Simulator,name=iPhone 16 Pro' test

Prefer MCP tools (build_sim, test_sim) over raw commands.

Key Patterns

Observable + SwiftData

  • SwiftData @Model classes are automatically Observable
  • DO NOT add @Observable to @Model classes (redundant, causes warnings)
  • Use @Bindable for two-way bindings to model properties in forms
  • Use @Query in views, modelContext.fetch() in non-view code

iCloud Sync

  • Automatic via SwiftData CloudKit integration
  • Conflict resolution: last-write-wins (CloudKit default)
  • Sync status exposed via CloudSyncManager.shared.syncState
  • Test sync by running on two simulators with same iCloud account

MCP Server Architecture

  • Runs as a local WebSocket server on port 8765
  • Exposes 6 tools: listAll, getList, createList, addItem, completeItem, deleteItem
  • Claude Desktop connects via MCP config in ~/.config/claude-desktop/config.json

Rules

  • NEVER modify .pbxproj or .xcodeproj contents
  • NEVER change the model schema without updating SampleData.swift
  • NEVER use ObservableObject — SwiftData models are already Observable
  • NEVER use @StateObject — use @State with @Observable classes
  • NEVER use NavigationView — always NavigationStack
  • NEVER add @Observable macro to @Model classes
  • ALWAYS use @Bindable for form bindings to model properties
  • ALWAYS test iCloud sync changes on two simulator instances
### Real CLAUDE.md: Reps (Minimal SwiftData App — 14 Files)

For small projects, the CLAUDE.md can be concise. Here is the pattern for Reps, a 14-file workout tracker. Notice how even a short CLAUDE.md covers all six essential sections:

```markdown
# Reps - Workout Tracking

**Bundle ID:** `com.941apps.Reps`
**Target:** iOS 26+
**Architecture:** SwiftUI + SwiftData
**Swift version:** 6.2

## File Structure

Reps/ ├── RepsApp.swift # App entry, model container ├── Models/ │ ├── Workout.swift # @Model: workout with exercises, date, duration │ ├── Exercise.swift # @Model: exercise with sets, reps, weight │ └── ExerciseTemplate.swift # @Model: saved exercise definitions ├── Views/ │ ├── WorkoutListView.swift # Main list of workouts │ ├── WorkoutDetailView.swift # Exercises within a workout │ ├── ExerciseRow.swift # Single exercise with inline editing │ ├── AddExerciseSheet.swift # Exercise selection from templates │ ├── NewWorkoutView.swift # Start new workout flow │ └── StatsView.swift # Progress charts and summaries ├── Managers/ │ └── WorkoutTimer.swift # Active workout timer └── Extensions/ └── Date+Extensions.swift # Formatting helpers

## Build & Test

```bash
xcodebuild -scheme Reps -destination 'platform=iOS Simulator,name=iPhone 16 Pro' build
xcodebuild -scheme Reps -destination 'platform=iOS Simulator,name=iPhone 16 Pro' test

SwiftData Relationships

  • Workout has many Exercise (cascade delete)
  • Exercise has optional ExerciseTemplate
  • ExerciseTemplate standalone (nullify on exercise delete)

Rules

  • NEVER modify .pbxproj
  • NEVER use ObservableObject — use @Observable
  • NEVER use NavigationView — use NavigationStack
  • @Model classes are already Observable — do not add @Observable macro
  • Use @Bindable for form bindings to model properties
That is 40 lines of CLAUDE.md for a 14-file project. It takes 10 minutes to write and saves hours of agent confusion.

### Real CLAUDE.md: Starfield Destroyer (SpriteKit + Metal — 32 Files)

Game projects require more framework-specific context. The agent needs to understand the scene graph, physics categories, and game state machine:

```markdown
# Starfield Destroyer - Space Shooter

**Bundle ID:** `com.941apps.StarfieldDestroyer`
**Target:** iOS 26+
**Architecture:** SpriteKit + Metal post-processing + Game Center
**Swift version:** 6.2

## Game Overview

99 levels across 3 galaxies. 8 unlockable ships with different stats.
Game Center leaderboards and achievements. Metal shader post-processing
for bloom and screen effects.

## File Structure

StarfieldDestroyer/ ├── StarfieldDestroyerApp.swift # App entry, Game Center auth ├── GameScene.swift # Main game scene, update loop ├── MenuScene.swift # Title screen, ship selection ├── Entities/ │ ├── PlayerShip.swift # Player node with physics, weapons, shields │ ├── EnemyShip.swift # Enemy base class with AI behaviors │ ├── Bullet.swift # Bullet pool node │ ├── PowerUp.swift # Collectible power-ups │ └── Boss.swift # Boss enemies (levels 33, 66, 99) ├── Systems/ │ ├── LevelManager.swift # Level progression, wave spawning │ ├── PhysicsCategory.swift # UInt32 bitmask categories │ ├── CollisionHandler.swift # Contact delegate methods │ ├── ScoreManager.swift # Score tracking, multipliers │ ├── ParticleManager.swift # Explosion, trail, shield emitters │ └── AudioManager.swift # Sound effects, background music ├── UI/ │ ├── HUDNode.swift # Score, health, level display │ ├── ShipSelectView.swift # SwiftUI ship selection (UIHostingController) │ ├── GameOverView.swift # Game over screen with score submission │ └── PauseMenu.swift # Pause overlay ├── Metal/ │ ├── MetalRenderer.swift # Post-processing render pipeline │ ├── BloomShader.metal # Bloom post-process effect │ └── ShaderTypes.h # Shared uniforms (bridging header) ├── Data/ │ ├── ShipData.swift # 8 ship definitions (speed, damage, shields) │ ├── LevelData.swift # 99 level configurations │ └── AchievementData.swift # Game Center achievement definitions └── GameCenterManager.swift # Leaderboard/achievement submission

## SpriteKit Scene Hierarchy

GameScene (SKScene) ├── backgroundLayer (zPosition: -100) │ └── StarfieldNode (parallax scrolling, 3 layers) ├── gameLayer (zPosition: 0) │ ├── playerShip (zPosition: 10) │ ├── enemyContainer (zPosition: 5) │ ├── bulletPool (zPosition: 8) — pre-allocated 50 bullets │ └── powerUpContainer (zPosition: 3) ├── effectsLayer (zPosition: 50) │ └── ParticleManager (explosion + trail emitters) └── hudLayer (zPosition: 100) ├── scoreLabel (SKLabelNode) ├── healthBar (custom SKShapeNode) └── levelLabel (SKLabelNode)

## Physics Categories

```swift
struct PhysicsCategory {
    static let none:      UInt32 = 0
    static let player:    UInt32 = 0b1        // 1
    static let enemy:     UInt32 = 0b10       // 2
    static let bullet:    UInt32 = 0b100      // 4
    static let powerUp:   UInt32 = 0b1000     // 8
    static let shield:    UInt32 = 0b10000    // 16
    static let bossBullet:UInt32 = 0b100000   // 32
}

// Contact pairs:
// player + enemy → damage
// player + powerUp → collect
// bullet + enemy → destroy
// player + bossBullet → damage

Game State Machine

.menu  .playing  .paused  .playing
                  .gameOver  .menu
                  .bossIntro  .playing
                  .levelComplete  .playing (next level)

Metal Post-Processing

  • Bloom shader: BloomShader.metal — multi-pass Gaussian blur + additive blend
  • Uniforms: PostProcessUniforms { float intensity; float threshold; float2 resolution; }
  • Applied after SpriteKit renders each frame via SKView.presentScene(:transition:)
  • DO NOT modify Metal shaders without testing on device

Build & Test

xcodebuild -scheme StarfieldDestroyer -destination 'platform=iOS Simulator,name=iPhone 16 Pro' build
xcodebuild -scheme StarfieldDestroyer -destination 'platform=iOS Simulator,name=iPhone 16 Pro' test

Rules

  • NEVER modify .pbxproj
  • NEVER modify PhysicsCategory bitmasks (breaks all collision detection)
  • NEVER change the scene hierarchy z-ordering without understanding render order
  • NEVER modify ShaderTypes.h without updating both Swift and Metal references
  • Add new enemies by subclassing EnemyShip, not by modifying it
  • Bullet pooling: recycle via removeFromParent() + re-add, never allocate new
  • Game Center: always check isAuthenticated before submitting scores
### Real CLAUDE.md: amp97 (Metal + Audio Visualization — 41 Files)

Metal projects need the most framework-specific context because agents cannot verify visual output:

```markdown
# amp97 - Audio Visualizer

**Bundle ID:** `com.941apps.amp97`
**Target:** iOS 26+
**Architecture:** Metal render pipeline + AVAudioEngine analysis
**Swift version:** 6.2

## Architecture

Audio Input (microphone/file) → AVAudioEngine tap → FFT (vDSP) → Frequency/amplitude buffers → Metal compute shader (analysis) → Metal render pipeline (visualization) → CADisplayLink (60fps) → MTKView

## File Structure

amp97/ ├── amp97App.swift # App entry ├── Audio/ │ ├── AudioEngine.swift # AVAudioEngine setup, tap installation │ ├── FFTProcessor.swift # vDSP FFT, frequency bin extraction │ ├── AudioBuffer.swift # Ring buffer for audio data │ └── MicrophoneManager.swift # Microphone permission, session config ├── Rendering/ │ ├── MetalView.swift # MTKView wrapper for SwiftUI │ ├── Renderer.swift # Main render loop, pipeline state │ ├── ShaderLibrary.swift # Compiled shader management │ ├── BufferManager.swift # Triple-buffered uniform updates │ └── TextureManager.swift # Offscreen render targets ├── Shaders/ │ ├── Shaders.metal # Vertex + fragment shaders │ ├── AudioCompute.metal # Audio analysis compute kernel │ ├── PostProcess.metal # Bloom, color grading │ └── ShaderTypes.h # Shared uniforms (bridging header) ├── Visualizations/ │ ├── WaveformViz.swift # Oscilloscope-style waveform │ ├── SpectrumViz.swift # Frequency spectrum bars │ ├── CircularViz.swift # Radial visualization │ └── VizSelector.swift # Visualization switching ├── Views/ │ ├── MainView.swift # Full-screen viz with overlays │ ├── ControlsOverlay.swift # Play/pause, viz selection, gain │ └── SettingsView.swift # Audio source, sensitivity └── Extensions/ ├── SIMD+Extensions.swift # Vector math helpers └── Color+Metal.swift # UIColor → float4 conversion

## Metal Pipeline

### Uniforms (ShaderTypes.h)
```c
typedef struct {
    float time;
    float2 resolution;
    float audioLevel;       // 0.0-1.0 RMS amplitude
    float frequencyBins[64]; // FFT output, normalized
    float4x4 transform;
} Uniforms;

Render Pipeline

  1. Compute pass: AudioCompute.metal processes FFT data → texture
  2. Render pass: Shaders.metal reads texture + uniforms → visualization
  3. Post-process pass: PostProcess.metal applies bloom → final output

Buffer Management

  • Triple buffering with DispatchSemaphore(value: 3)
  • Uniforms updated per-frame on CPU, consumed by GPU 1-2 frames later
  • Audio data ring buffer: 4096 samples, lock-free single producer/consumer

Rules

  • NEVER modify ShaderTypes.h without updating BOTH Swift and Metal sides
  • NEVER exceed 64 frequency bins (fixed buffer size in shader)
  • NEVER test Metal visual output in simulator — device only
  • NEVER modify the audio engine tap format (48kHz, mono, float32)
  • Triple buffer discipline: always signal semaphore in completion handler
  • Audio session: .playAndRecord category with .defaultToSpeaker option
### Scaling CLAUDE.md with Project Size

The right level of detail depends on file count and framework complexity:

| Project Size | CLAUDE.md Depth | Example |
|-------------|----------------|---------|
| **Small (< 20 files)** | Identity + file list + rules | Reps (14 files): basic SwiftData patterns, build commands, prohibitions |
| **Medium (20-40 files)** | + Framework context + key patterns | TappyColor (30 files): SpriteKit scene hierarchy, physics categories, game loop |
| **Large (40+ files)** | + Architecture diagrams + relationship maps + multi-target info | Return (63 files): cross-platform architecture, session sync diagram, per-platform differences |
| **Specialized (Metal/GPU)** | + Pipeline diagrams + shared type definitions + buffer layouts | amp97 (41 files): render pipeline stages, uniform struct, buffer management |

The cost of over-documenting is near zero (the agent skips what it does not need). The cost of under-documenting is high (the agent invents patterns that conflict with your codebase).

### CLAUDE.md Checklist

Use this checklist when creating or auditing a CLAUDE.md for an iOS project:

- [ ] Bundle ID and deployment target specified
- [ ] Swift version and architecture pattern named
- [ ] File structure with inline purpose annotations
- [ ] Build command with correct scheme and destination
- [ ] Test command with correct scheme and destination
- [ ] MCP preference noted ("prefer build_sim over xcodebuild")
- [ ] @Observable rule (never ObservableObject)
- [ ] NavigationStack rule (never NavigationView)
- [ ] .pbxproj prohibition
- [ ] Framework-specific context (HealthKit permissions, SwiftData relationships, SpriteKit hierarchy, Metal pipeline)
- [ ] Platform availability guards documented (#if canImport, #if os)
- [ ] Key singletons and shared patterns documented
- [ ] Known limitations or gotchas noted

---

## Your First Agent Session

With MCP configured and a CLAUDE.md in your project, here is a walkthrough of an effective first session. This uses Claude Code CLI but the workflow applies to any runtime.

### Step 1: Verify the Agent Can See Your Project

You: Summarize this project. What scheme should I build with?

Claude: [reads CLAUDE.md] [calls discover_projs via MCP] [calls list_schemes via MCP]

    This is Return, a zen meditation timer for iOS 26+, watchOS, and tvOS.
    Available schemes: Return, ReturnWatch, ReturnTV, ReturnTests.
    Primary scheme for iOS: Return.
If the agent does not reference your CLAUDE.md content, check that the file is in the project root (same directory as `.xcodeproj` or `Package.swift`).

### Step 2: Run a Health Check Build

You: Build the Return scheme for iOS simulator and report results.

Claude: [calls list_sims — finds iPhone 16 Pro on iOS 26.2] [calls boot_sim — boots the simulator] [calls build_sim — builds the project]

    Build succeeded. 63 source files compiled.
    2 warnings:
    - HomeView.swift:132 — UIScreen.main deprecated in iOS 26.0
    - LogWaterIntent.swift:61 — Result of try? is unused
    0 errors.
### Step 3: Run Tests

You: Run all tests and report pass/fail.

Claude: [calls test_sim]

    12 tests executed:
    ✓ TimerManagerTests (5/5 passed)
    ✓ SettingsTests (4/4 passed)
    ✓ ConstantsTests (3/3 passed)
    All tests passed.
### Step 4: Implement a Feature

You: Add a new “Rain” theme to the theme system. Follow the existing pattern in Theme.swift and ThemeManager.swift. Include a rain ambient sound. Build and test after implementation.

Claude: [reads Theme.swift — understands Theme struct pattern] [reads ThemeManager.swift — understands theme registration] [reads existing theme definitions] [adds Rain theme to Theme.swift following existing pattern] [registers Rain theme in ThemeManager.swift] [calls build_sim — build succeeds] [calls test_sim — all tests pass]

    Added Rain theme:
    - Theme.swift: new `static let rain` with video, cover, ambient
    - ThemeManager.swift: added to `allThemes` array
    - Build: success (0 errors, 2 existing warnings)
    - Tests: 12/12 passed

    Note: You'll need to add rain_ambient.wav to the project
    and add the new Theme.swift changes to the Xcode target manually
    (I did not modify .pbxproj).
The agent's note about not modifying .pbxproj is the result of the rules in CLAUDE.md. Without that rule, the agent would attempt to modify the project file and likely corrupt it.

---

## What Agents Do Well in iOS

These are the tasks where agents consistently produce correct, production-ready output with minimal human review.

### SwiftUI Views and Modifiers

Agents have deep pattern recognition for SwiftUI declarative syntax. View composition, modifier chains, state bindings, and layout — these map well to the agent's training data because SwiftUI's API surface is well-documented and patterns are highly consistent.

**Where agents excel:**
- Building new views from a description ("create a settings sheet with toggles for X, Y, Z")
- Applying modifier chains (`.glassEffect()`, `.sensoryFeedback()`, `.navigationTitle()`)
- Converting between layout patterns (VStack to LazyVGrid, List to ScrollView)
- Implementing `@Bindable` form bindings to SwiftData models
- Building preview providers with sample data

**Example prompt that produces excellent results:**

Create a SettingsView that matches the existing pattern in SettingsSheet.swift. Include toggles for: - Enable haptic feedback (Settings.shared.hapticsEnabled) - Enable HealthKit logging (Settings.shared.healthKitEnabled) - Show session history (navigation link to SessionHistoryView)

Use Liquid Glass styling with .glassEffect() on section backgrounds. Follow the @Observable pattern, not ObservableObject.

The specificity matters. "Create a settings view" produces generic output. "Create a SettingsView that matches the existing pattern in SettingsSheet.swift" produces output consistent with your codebase.

### SwiftData Models and Queries

Agents handle SwiftData's `@Model` macro, relationships, and `@Query` patterns reliably. The framework's declarative nature (similar to Django ORM or SQLAlchemy) maps well to patterns the agent has seen across many codebases.

**Where agents excel:**
- Defining `@Model` classes with relationships
- Writing `@Query` with sort descriptors and predicates
- Implementing CRUD operations via `modelContext`
- Migration plans between schema versions
- Preview data and test fixtures

**Where agents need guidance:**
- Complex `#Predicate` expressions (SwiftData's predicate DSL has limitations that the agent does not always know about — document known limitations in CLAUDE.md)
- CloudKit sync configuration (automatic via SwiftData, but the agent may try to implement manual sync)

### Unit Tests

Agent-written unit tests are consistently high quality for iOS projects. The agent understands XCTest patterns, async test methods, and setup/teardown lifecycle.

Write unit tests for TimerManager covering: 1. Initial state is .stopped 2. start() transitions to .running 3. pause() transitions to .paused 4. reset() returns to .stopped with original duration 5. Timer counts down correctly (test with 3-second duration)

The agent produces well-structured XCTest cases with `setUp()` and `tearDown()`, appropriate assertions, and async handling for timer-based tests.

### Refactoring and Pattern Application

Agents excel at mechanical refactoring: extracting views into components, converting `ObservableObject` to `@Observable`, migrating from `NavigationView` to `NavigationStack`, and applying consistent patterns across multiple files.

Refactor all views in the Views/ directory to use @Observable instead of ObservableObject. Update @StateObject to @State, @ObservedObject to direct property access, and @Published to plain properties.

The agent methodically works through each file, applies the transformation correctly, and maintains existing functionality. This is high-leverage work  a refactoring that would take an hour of manual editing completes in minutes with near-perfect accuracy.

### Build Error Diagnosis via MCP

With structured MCP output, agents diagnose build errors faster than most developers. The agent reads the error JSON, identifies the exact file and line, understands the error message, and applies the fix  often in a single turn.

**Errors agents fix autonomously:**
- Missing imports
- Type mismatches
- Protocol conformance gaps
- Deprecated API usage (with replacement)
- Missing required initializer parameters
- Access control violations

**Errors agents need help with:**
- Ambiguous type resolution (multiple modules define the same type)
- Complex generic constraint failures
- Macro expansion errors (the agent cannot see expanded macro output)

### Simulator Management

Agents handle simulator lifecycle well via MCP:

Boot an iPhone 16 Pro simulator on iOS 26, install the app, and take a screenshot.

The agent calls `list_sims` to find available runtimes, `boot_sim` to start the simulator, `build_sim` to build and install, and `screenshot` to capture  all through structured MCP calls.

---

## What Agents Do Poorly in iOS

Honest accounting of where agents fail. Knowing these boundaries prevents frustration and wasted tokens.

### .pbxproj File Modifications — NEVER

This is the single most important rule in iOS agent development. The `.pbxproj` file is Xcode's project configuration — a structured text file with UUID references, build phase listings, and target membership. It is nominally human-readable but practically unparseable for AI agents.

**Why agents fail at .pbxproj:**
- The file uses a custom format (not JSON, not YAML, not XML) with positional significance
- Every entry is cross-referenced by UUID — adding a file requires updating 3-5 different sections consistently
- A single misplaced character corrupts the entire project file
- Xcode's merge conflict resolution for .pbxproj is already fragile  agent edits make it worse

**What happens when an agent edits .pbxproj:**
1. The edit appears to succeed (the agent reports "file updated")
2. Xcode refuses to open the project ("The project file is corrupted")
3. You spend 15-60 minutes recovering from git history
4. You learn to add the PreToolUse hook (see [Hooks](#hooks-for-ios-development))

**The workflow:** The agent creates Swift files. You add them to the Xcode project manually (drag into Xcode, or File > Add Files). This takes 5 seconds per file and prevents hours of recovery.

**For Swift Package Manager projects:** This limitation is less severe. `Package.swift` is a standard Swift file that agents can edit reliably. If your project uses SPM exclusively (no .xcodeproj), the agent can manage the full project structure.

### Complex Interface Builder / Storyboard Edits

If your project uses Interface Builder (`.xib` files) or Storyboards (`.storyboard` files), agents cannot edit them meaningfully. These are XML files with auto-generated UUIDs, constraint references, and outlet connections that are designed for visual editing, not text editing.

**The mitigation:** Use SwiftUI exclusively for new views. If your project has legacy Interface Builder files, leave them alone and build new UI in SwiftUI.

### Performance Optimization

Agents write correct code but not necessarily performant code. They cannot profile your app, identify bottlenecks, or measure frame rates. Performance optimization requires:

1. Instruments profiling (visual tool, not agent-accessible)
2. Understanding of the specific device's GPU/CPU characteristics
3. Iterative measurement-driven changes

**Where this shows up:**
- Metal shader optimization (the agent writes valid Metal but cannot measure GPU frame time)
- SwiftUI view body complexity (the agent creates deeply nested views that cause redraw overhead)
- Core Data / SwiftData fetch optimization (the agent writes correct queries that may be slow on large datasets)

**The mitigation:** Use agents for implementation, profile manually with Instruments, then ask the agent to apply specific optimizations you have identified.

### Code Signing and Provisioning

Agents cannot debug code signing issues beyond reading the error message. Provisioning profile management, certificate creation, entitlement configuration, and App Store submission are fundamentally human-operated workflows that involve the Apple Developer portal, Keychain Access, and Xcode's signing UI.

**What the agent sees:** "Signing for 'Return' requires a development team."

**What the agent cannot see:** Whether your certificate expired, whether the provisioning profile includes the device, whether the bundle ID matches the App ID, or whether your entitlements file is correct.

**The mitigation:** Handle all signing in Xcode's Signing & Capabilities tab. Do not ask agents to debug signing failures.

### Complex Metal Shader Debugging

Agents write syntactically correct Metal Shading Language (MSL) but cannot verify visual output or debug GPU-side issues. Metal shaders execute on the GPU — the agent has no feedback mechanism for whether the shader produces correct visual results.

**What agents can do with Metal:**
- Write vertex and fragment shaders from descriptions
- Set up the Metal render pipeline in Swift
- Create compute shaders for data-parallel operations
- Fix compilation errors in `.metal` files

**What agents cannot do with Metal:**
- Verify visual correctness of shader output
- Debug GPU performance (frame time, occupancy, memory bandwidth)
- Diagnose visual artifacts (banding, precision issues, incorrect color space)
- Test on different GPU architectures (A-series vs. M-series behavior differences)

**The mitigation:** Test Metal shaders on physical devices. The Simulator's Metal implementation is not representative of device GPU behavior. Use Xcode's GPU Frame Capture for visual debugging.

### Visual Layout Verification

Agents cannot see your app's UI. They write SwiftUI layout code and can verify it compiles, but they cannot tell whether the resulting screen looks correct. A view that renders 10 pixels off-center, uses the wrong font weight, or has overlapping elements produces no build error and passes all logic tests.

**The mitigation:** Review UI changes visually. Use SwiftUI Previews in Xcode (or `RenderPreview` via Apple MCP for headless rendering) to verify layout. Consider snapshot testing with libraries like swift-snapshot-testing for automated visual regression detection.

---

## Hooks for iOS Development

Hooks are shell commands that execute deterministically at specific points in the agent's workflow. They are the enforcement mechanism — the difference between "please do not edit .pbxproj" (a suggestion the agent may ignore) and "you cannot edit .pbxproj" (a hard block).

For background on the hook system, see the [Claude Code hooks guide](/blog/claude-code-hooks-tutorial). This section covers iOS-specific hook patterns.

### PreToolUse: Block .pbxproj Writes

The most important hook in any iOS project. This blocks the agent from writing to `.pbxproj` files, `.xcodeproj/` directories, and other Xcode-managed files:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.(pbxproj|xcworkspace|xib|storyboard)$|xcodeproj/|xcworkspace/\"; then echo \"BLOCKED: Do not modify Xcode project files. Create Swift files and add to Xcode manually.\" >&2; exit 2; fi'"
      }
    ]
  }
}

Place this in .claude/settings.json at the project root or in ~/.claude/settings.json for global protection.

How it works: When the agent attempts to use the Edit or Write tool on any file matching the pattern, the hook runs, detects the file path, prints a warning to stderr, and exits with code 2 (which blocks the tool use). The agent receives the error message and adjusts its approach.

What it catches: - Direct .pbxproj edits - Any file inside .xcodeproj/ or .xcworkspace/ directories - Interface Builder files (.xib, .storyboard)

PostToolUse: Format-on-Save with SwiftFormat

Automatically format Swift files whenever the agent writes or edits them:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.swift$\"; then swiftformat \"$FP\" --quiet 2>/dev/null; fi'"
      }
    ]
  }
}

Requirements: SwiftFormat must be installed (brew install swiftformat).

Why this matters: Agents produce syntactically correct Swift but do not consistently follow formatting conventions. SwiftFormat normalizes indentation, brace placement, and import ordering. The format-on-save hook means every Swift file the agent touches is automatically formatted before you see it.

Optional: Add a .swiftformat configuration file to your project root to customize formatting rules:

# .swiftformat
--indent 4
--allman false
--stripunusedargs closure-only
--importgrouping testable-bottom
--header strip

PostToolUse: Auto-Run SwiftLint

If you use SwiftLint, run it after every Swift file edit:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.swift$\"; then swiftlint lint --path \"$FP\" --quiet 2>/dev/null || true; fi'"
      }
    ]
  }
}

The || true prevents lint warnings from blocking the agent. If you want lint violations to block, remove it.

PostToolUse: Auto-Build After Changes

For aggressive feedback loops, trigger a build after every Swift file change:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.swift$\"; then xcodebuild -scheme Return -destination \"platform=iOS Simulator,name=iPhone 16 Pro\" build 2>&1 | tail -5; fi'"
      }
    ]
  }
}

Warning: This is expensive. Every file edit triggers a build. Use sparingly — it is most useful during debugging sessions where you want immediate build feedback. For normal development, let the agent trigger builds manually via MCP when it is ready.

PreToolUse: Block Entitlement Modifications

Protect your entitlements file from accidental agent modifications:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.entitlements$\"; then echo \"BLOCKED: Do not modify entitlements files without explicit permission.\" >&2; exit 2; fi'"
      }
    ]
  }
}

Combined iOS Hook Configuration

Here is the full .claude/settings.json I use across all iOS projects:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.(pbxproj|xcworkspace|xib|storyboard|entitlements)$|xcodeproj/|xcworkspace/\"; then echo \"BLOCKED: Do not modify Xcode-managed files. Create Swift files and add manually.\" >&2; exit 2; fi'"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.swift$\"; then swiftformat \"$FP\" --quiet 2>/dev/null; fi'"
      }
    ]
  }
}

This gives you two guarantees: 1. The agent cannot corrupt Xcode project files (PreToolUse block) 2. Every Swift file the agent touches is auto-formatted (PostToolUse format)


Architecture Patterns That Work with Agents

Not all Swift architectures are equally agent-friendly. These patterns produce the best results because they are explicit, consistent, and well-represented in training data.

@Observable (Never ObservableObject)

iOS 26+ targets should use @Observable exclusively. This is both the modern pattern and the agent-friendly pattern:

// CORRECT — @Observable
@Observable
@MainActor
final class TimerManager {
    var timeRemaining: TimeInterval = 0
    var state: TimerState = .stopped

    func start() {
        state = .running
        // ...
    }
}

// In a view:
struct TimerView: View {
    @State private var timer = TimerManager()

    var body: some View {
        Text(timer.timeRemaining, format: .number)
    }
}
// WRONG — ObservableObject (deprecated pattern)
class TimerManager: ObservableObject {
    @Published var timeRemaining: TimeInterval = 0
    @Published var state: TimerState = .stopped
}

// WRONG — @StateObject (deprecated pattern)
struct TimerView: View {
    @StateObject private var timer = TimerManager()
}

Why @Observable is agent-friendly: The pattern is simpler (no @Published annotations needed), the ownership model is clearer (@State instead of @StateObject vs. @ObservedObject), and agents produce fewer bugs with it because there are fewer moving parts.

Document this in CLAUDE.md: Even with iOS 26 as the target, agents occasionally revert to ObservableObject patterns from their training data. Explicit prohibition prevents this.

// CORRECT
NavigationStack {
    List(items) { item in
        NavigationLink(value: item) {
            ItemRow(item: item)
        }
    }
    .navigationDestination(for: Item.self) { item in
        ItemDetailView(item: item)
    }
}

// WRONG
NavigationView {
    List(items) { item in
        NavigationLink(destination: ItemDetailView(item: item)) {
            ItemRow(item: item)
        }
    }
}

NavigationStack is iOS 16+ and the only navigation pattern you should use for new code. The type-safe navigationDestination(for:) pattern prevents the agent from creating incorrect navigation links.

SwiftData for Persistence

SwiftData models are the cleanest persistence pattern for agent-assisted development:

@Model
final class GroceryItem {
    var name: String
    var quantity: Int
    var isCompleted: Bool
    var category: Category?
    var list: GroceryList?

    init(name: String, quantity: Int = 1) {
        self.name = name
        self.quantity = quantity
        self.isCompleted = false
    }
}

Key rules for agents working with SwiftData: 1. @Model classes are automatically Observable — do not add @Observable 2. Use @Bindable for form bindings: @Bindable var item: GroceryItem 3. Use @Query in views for reactive data: @Query var items: [GroceryItem] 4. Use modelContext.fetch() in non-view code 5. Relationship deletions need explicit rules: .cascade, .nullify, .deny

Swift 6.2 Concurrency

Target Swift 6.2 strict concurrency for new projects:

// Actor isolation for shared mutable state
@MainActor
@Observable
final class DataManager {
    var items: [Item] = []

    func loadItems() async throws {
        let fetched = try await api.fetchItems()
        items = fetched  // Safe: @MainActor isolated
    }
}

// Sendable conformance for cross-actor transfers
struct Item: Sendable, Identifiable {
    let id: UUID
    let name: String
    let createdAt: Date
}

Agent guidance for concurrency: - Mark all view models @MainActor (prevents data race warnings) - Use async/await for all asynchronous work (no completion handlers) - Make value types Sendable for cross-actor transfers - Use Task { } in views for async initialization - Use nonisolated only when you have measured a performance need

Liquid Glass Design System (iOS 26+)

iOS 26 introduced the Liquid Glass design system. Agents handle it well when given explicit guidance:

// Glass effect on containers
VStack {
    // content
}
.glassEffect()

// Glass effect with tint
Button("Action") { }
    .glassEffect(.regular.tint(.blue))

// Glass effect on navigation bars (automatic in iOS 26)
NavigationStack {
    // content
}
// Navigation bar automatically uses glass material

// Custom glass shapes
RoundedRectangle(cornerRadius: 16)
    .fill(.ultraThinMaterial)
    .glassEffect()

Include in CLAUDE.md: “Use .glassEffect() on section backgrounds and card containers. Navigation bars automatically adopt glass material in iOS 26. Do not manually recreate glass effects with custom materials — use the system modifier.”


Framework-Specific Context

Each Apple framework has agent-specific considerations. This section covers the frameworks used across the 8 apps.

HealthKit

Apps using it: Return, Water

HealthKit requires careful permission handling and platform guards:

// Always check availability and authorization
import HealthKit

@MainActor
@Observable
final class HealthKitManager {
    private let store = HKHealthStore()
    var isAuthorized = false

    func requestAuthorization() async {
        guard HKHealthStore.isHealthDataAvailable() else { return }

        let types: Set<HKSampleType> = [
            HKQuantityType(.dietaryWater),
            HKCategoryType(.mindfulSession)
        ]

        do {
            try await store.requestAuthorization(toShare: types, read: types)
            isAuthorized = true
        } catch {
            // User denied — do not retry automatically
        }
    }
}

Agent rules for HealthKit: - Always guard with HKHealthStore.isHealthDataAvailable() - Never assume authorization — check on every write - Use #if canImport(HealthKit) for multi-platform code (HealthKit unavailable on tvOS) - Never store health data locally beyond what HealthKit provides - Include both NSHealthShareUsageDescription and NSHealthUpdateUsageDescription in Info.plist

SpriteKit

Apps using it: TappyColor, Starfield Destroyer

SpriteKit’s scene graph model requires explicit agent guidance:

## SpriteKit Rules

- Scene hierarchy is a tree of SKNodes with zPosition ordering
- Physics bodies use category bitmasks (UInt32) for collision detection
- Node pooling: pre-allocate reusable nodes (bullets, particles)
- Never add nodes directly to the scene — use layer nodes for organization
- Update loop: `update(_ currentTime:)` runs every frame — keep it fast
- Actions: use SKAction sequences for animations, not manual property updates
- Textures: use texture atlases for performance (.atlas directories)

Agent strengths with SpriteKit: - Creating SKAction sequences and groups - Setting up physics bodies and contact detection - Implementing game state machines - Building HUD overlays

Agent weaknesses with SpriteKit: - Performance-sensitive game loops (the agent adds unnecessary work per frame) - Complex physics simulations (custom physics beats SKPhysicsBody for precision) - Particle effect tuning (visual, requires iteration)

Metal

Apps using it: amp97, Water, Starfield Destroyer

Metal is the framework where agents struggle most. The GPU programming model is fundamentally different from CPU-side Swift, and agents cannot verify visual output.

## Metal Rules

- Shared types between Swift and Metal go in a bridging header (ShaderTypes.h)
- Triple buffer in-flight frames (semaphore with value 3)
- Test shaders on DEVICE, not simulator (Metal behavior differs)
- Compute shaders: threadgroup size must divide evenly into grid size
- Fragment shaders: output color must be in correct color space (sRGB or linear)
- DO NOT optimize shaders without Instruments GPU profiling data

What to include in CLAUDE.md for Metal projects: - The Uniforms struct definition (shared between Swift and MSL) - The render pipeline state setup pattern - Buffer indices and their purposes - Which shaders exist and what each does - Known precision issues (half vs. float)

Live Activities

Apps using it: Return

Live Activities require specific configuration that agents handle well once documented:

## Live Activities

- ActivityAttributes defined in `TimerActivityAttributes.swift`
- ActivityKit framework: `import ActivityKit`
- Widget extension: `ReturnWidgets/ReturnLiveActivity.swift`
- Start: `Activity<TimerActivityAttributes>.request(attributes:content:)`
- Update: `activity.update(ActivityContent(state:staleDate:))`
- End: `activity.end(ActivityContent(state:staleDate:), dismissalPolicy:)`
- Push token: register for updates via `activity.pushTokenUpdates`

Game Center

Apps using it: Starfield Destroyer

## Game Center

- Authentication: `GKLocalPlayer.local.authenticateHandler`
- Leaderboards: `GKLeaderboard.submitScore(_:context:player:leaderboardIDs:completionHandler:)`
- Achievements: `GKAchievement.report(_:withCompletionHandler:)` (takes `[GKAchievement]` array)
- Always check `GKLocalPlayer.local.isAuthenticated` before submitting
- Handle authentication failure gracefully (offline play must work)

Multi-Platform Patterns

Return spans iOS, watchOS, and tvOS. Multi-platform development with agents requires explicit platform boundary documentation.

Shared Code Organization

Shared/
├── MeditationSession.swift    # Data model (all platforms)
├── SessionStore.swift         # iCloud sync (all platforms)
└── SessionHistoryView.swift   # UI (adapts per platform)

Return/                        # iOS-specific
ReturnWatch Watch App/         # watchOS-specific
ReturnTV/                      # tvOS-specific

Rule for agents: “If a file is in Shared/, changes affect all platforms. If a file is in a platform directory, changes are isolated. Always check which directory a file is in before modifying it.”

Platform Availability Guards

// HealthKit: available on iOS and watchOS, not tvOS
#if canImport(HealthKit)
import HealthKit
// HealthKit code here
#endif

// ActivityKit: available on iOS only
#if canImport(ActivityKit)
import ActivityKit
// Live Activity code here
#endif

// WatchKit: available on watchOS only
#if os(watchOS)
import WatchKit
// Watch-specific code here
#endif

Agent guidance: “Always use #if canImport() or #if os() guards when using platform-specific frameworks. Do not assume a framework is available on all targets.”

Per-Platform UI Adaptation

struct SessionHistoryView: View {
    @Query var sessions: [MeditationSession]

    var body: some View {
        List(sessions) { session in
            SessionRow(session: session)
        }
        #if os(tvOS)
        .focusable()
        #endif
        #if os(iOS)
        .swipeActions {
            Button("Delete", role: .destructive) {
                // delete
            }
        }
        #endif
    }
}

Advanced Workflows

Autonomous Build-Test-Fix Loops

The most powerful pattern: give the agent a feature specification and let it iterate through build-test-fix cycles autonomously.

Implement a countdown timer that:
1. Starts from a user-selected duration (10, 20, or 30 minutes)
2. Shows remaining time with a circular progress indicator
3. Plays a bell sound on completion
4. Logs the session to HealthKit as mindful minutes

Build after each change. Fix all errors. Run tests when the build succeeds.
Continue until all tests pass and the build is clean.

The agent writes code, builds via MCP, reads structured errors, fixes them, and repeats. A feature that would require 5-10 human build-error-fix cycles completes in a single autonomous loop.

When this works: Well-defined features with clear acceptance criteria.

When this breaks: Open-ended features (“make it look nice”), performance-sensitive code, or anything requiring visual verification.

Subagent Delegation for iOS

Claude Code’s subagent system works for iOS projects:

Use a subagent to research the best approach for implementing
iCloud key-value store sync for meditation sessions across iOS,
watchOS, and tvOS. Report back with the recommended pattern.

The subagent explores documentation and code patterns in a separate context window, returns a summary, and the main session implements the recommendation. This prevents research from consuming your primary context.

Cross-App Pattern Application

When you maintain multiple iOS apps with consistent patterns, agents can apply patterns from one app to another:

Look at how Settings.swift works in the Return project
(centralized singleton with validation). Apply the same pattern
to create a Settings.swift for the Water project.

The agent reads the source pattern, understands the structure, and creates a consistent implementation in the target project.

Dual-Agent Review (Claude + Codex)

For critical changes, use two agents from different model families:

  1. Claude Code writes the implementation
  2. Codex CLI reviews it in a separate pass
# After Claude implements the feature:
codex "Review the changes in the last commit. Focus on Swift 6.2
      concurrency correctness, SwiftData relationship integrity,
      and potential retain cycles. Report issues only — no praise."

Different model families catch different classes of errors. This is especially valuable for Metal shaders and concurrency patterns where subtle bugs are easy to introduce.

What dual review catches that single review misses:

Issue Type Claude Strength Codex Strength
SwiftData relationship cycles Moderate Strong (GPT-4o)
@MainActor isolation gaps Strong Moderate
Metal buffer alignment Moderate Moderate
Retain cycle detection Strong (Opus) Strong (o3)
API deprecation awareness Strong (newer training data) Moderate
Concurrency race conditions Strong Strong (different patterns caught)

The dual review is not about finding more bugs — it is about finding different bugs. Each model family has different failure modes in its pattern recognition.

Batch Operations Across Multiple Apps

When a framework or pattern change affects multiple apps:

# Update @Observable pattern across all projects
for project in BananaList Return Water Reps; do
  cd ~/Projects/$project
  claude -p "Audit all files for any remaining ObservableObject usage.
             Convert to @Observable following the pattern in CLAUDE.md.
             Build and test after changes." --dangerously-skip-permissions
done

Use with caution. The --dangerously-skip-permissions flag is required for non-interactive mode but bypasses all safety checks. Ensure your PreToolUse hooks are in place to protect .pbxproj files.


Real-World Case Studies

Abstract advice is easy. Here are specific scenarios from the 8 apps that illustrate how agent-assisted iOS development works in practice — including the failures.

Case Study 1: Adding a TV App to Return (Success)

The task: Add a tvOS target to Return, a meditation timer that already had iOS and watchOS versions. The TV app needed Siri Remote navigation, large-screen UI, and settings sync with the iOS app.

What the agent did well: - Read the existing iOS TimerManager and created a TVTimerManager that omitted Live Activities and HealthKit (unavailable on tvOS) - Created custom button styles for Siri Remote focus navigation (TVCapsuleButtonStyle, TVCircleButtonStyle) - Built a TVStepper component that replaces wheel pickers (not usable with Siri Remote) with +/- buttons - Implemented settings sync via App Groups (group.com.941apps.Return) - Added #if os(tvOS) guards throughout shared code - Built and tested via MCP with platform=tvOS Simulator,name=Apple TV

What I had to do manually: - Create the tvOS target in Xcode (File > New > Target > tvOS App) - Add the new target to the Xcode project (.pbxproj changes) - Configure App Groups entitlement for the TV target - Add the TV target to the existing scheme or create a new one - Manually add all the agent-created Swift files to the TV target - Test Siri Remote navigation by hand (the agent cannot evaluate focus behavior)

Result: 15 new Swift files, fully functional TV app, implemented in approximately 3 hours of agent-assisted work. The equivalent manual implementation would have taken 1-2 days. The agent handled approximately 80% of the code, I handled the 20% that required Xcode UI interaction and manual testing.

Case Study 2: Metal Shader Debugging in amp97 (Partial Failure)

The task: Add an energy-based intensity system to the oscilloscope shader. The visualization should pulse with audio energy.

What happened: 1. The agent wrote a valid Metal shader modification adding a uEnergy uniform and HDR tonemapping 2. The code compiled without errors 3. On device, the visualization was completely white — the intensity coefficient was 10x too high (3.5 instead of 0.30) 4. The agent could not see the white screen, so it had no feedback signal 5. I identified the issue visually and asked the agent to reduce the coefficient 6. The agent reduced it, but the overall energy state machine was too complex and broke the visualizer in different ways 7. Reverted entirely — two commits (67959ed and cda4830) reverted in 869d914

The lesson: Metal shaders are the hardest domain for agent-assisted development because the feedback loop is broken. The agent can verify syntax (compiles) and semantics (correct types) but cannot verify output (looks right). Any shader modification that changes visual behavior requires human verification on device.

What I added to CLAUDE.md after this: “DO NOT attempt energy state modifications to the oscilloscope shader without extremely careful coefficient testing. Previous attempt broke the visualizer with coefficients 10x too high.”

Case Study 3: SwiftData Migration in Banana List (Success)

The task: Migrate the data model from V1 to V2, adding a quantity field to GroceryItem and a new Category model with relationships.

What the agent did: 1. Read the existing V1 model definitions 2. Created V2 model definitions with the new fields and relationships 3. Wrote a GroceryMigrationPlan with SchemaMigrationPlan protocol conformance 4. Implemented the V1toV2 migration stage: added default quantity: 1 and category: nil 5. Updated all views to support the new fields 6. Updated SampleData.swift for previews 7. Built and ran tests via MCP — all passed 8. Created migration-specific unit tests

The key: The agent succeeded because SwiftData migrations follow a well-defined protocol pattern that is thoroughly represented in Apple’s documentation and training data. The CLAUDE.md explicitly documented the V1 model, so the agent understood what it was migrating from.

Case Study 4: iCloud Session Sync in Return (Success with Complexity)

The task: Implement cross-device meditation session logging. Sessions completed on Apple TV or Mac should sync to iPhone for HealthKit logging.

What the agent produced:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
    tvOS              Mac              Watch     
 TVTimerMgr        TimerMgr          WatchTimer  
└──────┬──────┘     └──────┬──────┘     └──────┬──────┘
                                             
       └───────────────────┼───────────────────┘
                           
                           
              ┌────────────────────────┐
                   SessionStore       
                (iCloud Key-Value)    
              └───────────┬────────────┘
                          
                          
              ┌────────────────────────┐
                iPhone (on foreground)
                 Write to HealthKit  
              └────────────────────────┘

The agent: 1. Created MeditationSession data model with UUID, dates, duration, source device, and HealthKit sync status 2. Built SessionStore singleton managing NSUbiquitousKeyValueStore for iCloud sync 3. Implemented merge conflict resolution (UUID-based deduplication) 4. Added SessionHistoryView with platform-specific adaptations (swipe-to-delete on iOS, focus-based on tvOS) 5. Wired iPhone-side HealthKit sync for sessions from other devices

What required iteration: The initial implementation did not handle the case where iPhone app launches in the background (no foreground notification for sync). The agent needed specific guidance: “Use NSUbiquitousKeyValueStore.didChangeExternallyNotification to trigger sync on background KV changes.” After that hint, the implementation was correct.

The lesson: Agents handle multi-platform architectural patterns well when the architecture is clearly described. The iCloud sync pattern is not trivial, but it follows a documented Apple pattern that the agent understood. The edge case (background sync) required human domain knowledge because it is not well-documented.

Case Study 5: Game Center Integration in Starfield Destroyer (Success)

The task: Add Game Center leaderboards and achievements to the space shooter.

What the agent did well: - Implemented GKLocalPlayer.local.authenticateHandler in the app entry point - Created a GameCenterManager with methods for score submission and achievement reporting - Added authentication state checking before all Game Center operations - Handled the offline case gracefully (game plays without Game Center, submits when reconnected) - Created achievement definitions matching the 8-ship progression system

What required manual work: - Creating the leaderboards and achievements in App Store Connect (web portal, not agent-accessible) - Configuring the Game Center entitlement in Xcode - Testing with a sandbox Game Center account (requires manual sign-in on device)


Project Lifecycle with Agents

Starting a New iOS Project

The optimal workflow for starting a new project with agent assistance:

Phase 1: Human Setup (15-30 minutes) 1. Create the Xcode project (File > New > Project) 2. Configure signing and capabilities 3. Set deployment target and supported destinations 4. Add any required entitlements (HealthKit, Game Center, etc.) 5. Create the initial CLAUDE.md with project identity and rules

Phase 2: Agent Implementation (hours to days) 1. Agent creates the data model (SwiftData, Core Data, or plain structs) 2. Agent builds views following your documented patterns 3. Agent implements business logic in manager/service classes 4. Agent writes unit tests 5. Build-test-fix loop via MCP (autonomous)

Phase 3: Human Integration (30-60 minutes) 1. Add agent-created files to Xcode targets 2. Verify signing and entitlements 3. Test on physical device 4. Review visual layout and UX 5. Submit to App Store Connect

Maintaining an Existing Project

For ongoing development on established apps:

You: Here's the next feature for Return: add a "Focus Score" that
     calculates from session duration and consistency. Show it on
     the main timer view. Build and test.

Claude: [reads CLAUDE.md, understands patterns]
        [reads TimerManager.swift, Settings.swift]
        [creates FocusScore.swift  model with calculation logic]
        [creates FocusScoreView.swift  display component]
        [integrates into ContentView.swift]
        [calls build_sim  1 error: missing import]
        [fixes import]
        [calls build_sim  success]
        [writes FocusScoreTests.swift]
        [calls test_sim  all pass]

The agent’s effectiveness scales with how well your CLAUDE.md reflects the project’s current state. Update your CLAUDE.md when you add significant new features, change architectural patterns, or introduce new frameworks.

When to Involve the Agent vs. Not

Task Agent? Why
New SwiftUI view Yes Agents excel at declarative UI
SwiftData model changes Yes Well-defined, testable
Unit tests Yes Mechanical, pattern-based
Refactoring Yes Systematic, multi-file
Build error diagnosis Yes (via MCP) Structured feedback loop
New Xcode target No Requires Xcode UI, .pbxproj changes
Signing and provisioning No Portal-based, not agent-accessible
Visual polish No Requires human aesthetic judgment
Metal shader tuning No Requires device GPU testing
App Store submission No Portal and Xcode Organizer
Performance profiling No Requires Instruments
Accessibility audit Partial Agent can add labels, human verifies VoiceOver

Configuring Agent Definitions

If you use Claude Code’s agent definition system (.claude/agents/), create an iOS-specific agent:

---
name: ios-developer
description: iOS development agent with MCP build tools and SwiftUI expertise
tools:
  - XcodeBuildMCP
  - xcode
---

# iOS Developer Agent

You are an iOS development agent for apps targeting iOS 26+ with SwiftUI.

## Architecture Rules
- @Observable for all view models (NEVER ObservableObject)
- NavigationStack for all navigation (NEVER NavigationView)
- SwiftData for persistence
- Swift 6.2 strict concurrency
- @MainActor on all Observable classes

## Build & Test — Always Use MCP

Prefer MCP tools over raw shell commands for ALL build operations:

- **Build**: `build_sim` / `build_device` (NOT `xcodebuild` via Bash)
- **Test**: `test_sim` / `test_device` (NOT `xcodebuild test` via Bash)
- **Simulators**: `list_sims`, `boot_sim`, `open_sim`
- **Debug**: `debug_attach_sim`, `debug_stack`, `debug_variables`
- **Apple docs**: `DocumentationSearch` (NOT WebSearch for Apple APIs)
- **Swift verification**: `ExecuteSnippet` (NOT `swift` via Bash)

MCP returns structured JSON. Bash returns unstructured text.

## File Management Rules
- NEVER modify .pbxproj, .xcodeproj/, .xcworkspace/, .xib, .storyboard
- Create Swift files in the correct directory
- Report files that need manual addition to Xcode targets

## SwiftData Rules
- @Model classes are automatically Observable — do not add @Observable
- Use @Bindable for form bindings to model properties
- Use @Query in views, modelContext.fetch() elsewhere
- Document relationship delete rules

## When You Get Stuck
- Build errors: use `build_sim` via MCP for structured output
- API questions: use `DocumentationSearch` via Apple MCP
- Swift verification: use `ExecuteSnippet` via Apple MCP
- Never guess — verify with tools

Reference this agent with @ios-developer in Claude Code sessions.


Testing Patterns for Agent-Assisted iOS

Agents write excellent unit tests when given clear guidance. Here are the patterns that produce the best results.

Test File Organization

# In CLAUDE.md:
## Test Structure

Tests mirror source structure:
- `ReturnTests/TimerManagerTests.swift` tests `TimerManager.swift`
- `ReturnTests/SettingsTests.swift` tests `Settings.swift`
- `ReturnTests/ConstantsTests.swift` tests `Constants.swift`

Test naming: `test_<what>_<condition>_<expected>`
Example: `test_start_whenStopped_transitionsToRunning`

Prompting for Tests

Effective test prompt:

Write unit tests for TimerManager covering:

1. Initial state is .stopped with timeRemaining == selectedDuration
2. start() transitions state to .running
3. pause() from .running transitions to .paused
4. reset() from any state returns to .stopped with original duration
5. start() from .paused resumes (state becomes .running)
6. Edge case: reset() when already stopped is a no-op
7. Edge case: pause() when already paused is a no-op

Follow the existing test pattern in SettingsTests.swift.
Use setUp() to create a fresh TimerManager for each test.

Why this works: Numbered acceptance criteria give the agent a checklist. Referencing an existing test file establishes the pattern. Specifying setUp() usage prevents the agent from creating tangled test state.

Ineffective test prompt:

Write tests for TimerManager.

This produces generic, shallow tests that miss edge cases and may not follow your project’s patterns.

Async Test Patterns

For testing timer-based and async code:

// Agent produces this pattern when guided correctly:
final class TimerManagerTests: XCTestCase {
    var sut: TimerManager!

    @MainActor
    override func setUp() {
        super.setUp()
        sut = TimerManager()
    }

    @MainActor
    func test_start_whenStopped_transitionsToRunning() {
        // Given
        XCTAssertEqual(sut.state, .stopped)

        // When
        sut.start()

        // Then
        XCTAssertEqual(sut.state, .running)
    }

    @MainActor
    func test_timerCountsDown_afterOneSecond() async throws {
        // Given
        sut.selectedDuration = 10
        sut.reset()
        sut.start()

        // When
        try await Task.sleep(for: .seconds(1.1))

        // Then
        XCTAssertLessThanOrEqual(sut.timeRemaining, 9.0)
    }
}

Key patterns agents need reminding about: - @MainActor on test methods that test @MainActor classes - async throws for tests that use Task.sleep or async operations - Tolerance in time-based assertions (1.1 seconds, not exactly 1.0) - Clean setUp() / tearDown() for test isolation

Snapshot Testing

For visual regression detection, consider adding swift-snapshot-testing:

Add snapshot tests for the main timer view in three states:
1. Stopped (showing full duration)
2. Running (showing countdown)
3. Completed (showing 00:00 with completion state)

Use SnapshotTesting library. Create reference images on first run.

Agents set up snapshot tests correctly but cannot review the reference images. You review the initial snapshots, then the agent’s tests catch visual regressions in future changes.


Context Window Management for iOS Projects

The 1M context window (Opus 4.6) is large but not infinite. iOS projects have specific context management considerations.

Token Cost of iOS Files

File Type Typical Size Approximate Tokens
SwiftUI view (simple) 50-100 lines 500-1,000
SwiftUI view (complex) 200-400 lines 2,000-4,000
SwiftData model 30-80 lines 300-800
Manager/service class 100-300 lines 1,000-3,000
Metal shader (.metal) 50-200 lines 500-2,000
Unit test file 50-200 lines 500-2,000
CLAUDE.md 100-300 lines 1,000-3,000
MCP response (build) varies 200-2,000
MCP response (test) varies 500-5,000

For a 50-file project: Reading all files consumes approximately 50,000-100,000 tokens — well within the 1M window. The agent can hold the entire project in context.

For a 100+ file project: Selective reading becomes necessary. The agent reads CLAUDE.md first (for the file structure annotations), then reads specific files as needed. This is why file annotations in CLAUDE.md are critical — they guide the agent to the right files without reading everything.

Strategies for Large Projects

  1. Detailed CLAUDE.md file annotations — The agent reads the file map and navigates directly to relevant files
  2. Subagent delegation — Route exploration and research to subagents (clean context, returns summaries)
  3. Focused prompts — “Modify SettingsView.swift to add a new toggle” is better than “update the settings”
  4. Session boundaries — Start new sessions for unrelated features rather than extending a long session
  5. Use /compact — Claude Code’s compaction command summarizes the conversation and frees context

MCP Token Efficiency

One of the strongest arguments for MCP: structured JSON responses consume far fewer tokens than raw xcodebuild output.

Scenario Raw Bash Tokens MCP Tokens Savings
Successful build 3,000-10,000 200-500 85-95%
Failed build (1 error) 3,000-10,000 300-800 90-92%
Test results (20 tests) 2,000-5,000 500-1,000 75-80%
Simulator list 500-2,000 200-400 60-80%

Over a typical development session with 10-20 build cycles, MCP saves 30,000-150,000 tokens compared to raw xcodebuild — tokens that remain available for actual code reasoning.


Troubleshooting

“build_sim failed — scheme not found”

The agent is guessing the scheme name. Fix:

Use discover_projs and list_schemes to find the correct scheme name
for this project before building.

Or add the scheme name explicitly to your CLAUDE.md:

## Build
Primary scheme: `Return` (iOS)
Watch scheme: `ReturnWatch` (watchOS)
TV scheme: `ReturnTV` (tvOS)

“xcrun mcpbridge — command not found”

You need Xcode 26.3 or later. Check with xcodebuild -version. If you have Xcode 26.3+ but the command still fails:

# Ensure Xcode command line tools are selected
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

# Verify
xcrun mcpbridge --help

“MCP tools not appearing in Claude Code”

MCP tools registered mid-session may not appear until restart. Exit Claude Code and start a fresh session:

# Exit current session (Ctrl+C or /exit)
# Start fresh
claude

Then verify:

You: List all available MCP tools from XcodeBuildMCP.

“Agent keeps using xcodebuild via Bash instead of MCP”

The agent is not discovering MCP tools via Tool Search. Two fixes:

  1. Add explicit guidance to CLAUDE.md (see Teaching the Agent to Use MCP)
  2. Prompt directly: “Use the build_sim MCP tool, not xcodebuild via Bash”

“Build succeeds but agent reports failure”

XcodeBuildMCP parses xcodebuild output. If the build produces warnings that look like errors (common with deprecation warnings), the agent may misinterpret the result. Check the actual status field in the MCP response.

“Simulator hangs during boot”

Kill all simulators and reboot:

xcrun simctl shutdown all
xcrun simctl boot "iPhone 16 Pro"

Or ask the agent:

Shut down all simulators, then boot a fresh iPhone 16 Pro.

“Agent tried to modify .pbxproj despite CLAUDE.md rules”

CLAUDE.md rules are suggestions. Hooks are enforcement. If you do not have the PreToolUse hook blocking .pbxproj writes, the agent will eventually attempt it. Install the hook:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "bash -c 'INPUT=$(cat); FP=$(echo \"$INPUT\" | jq -r \".tool_input.file_path // empty\"); if echo \"$FP\" | grep -qE \"\\.(pbxproj|xcworkspace|xib|storyboard)$|xcodeproj/|xcworkspace/\"; then echo \"BLOCKED: Do not modify Xcode project files.\" >&2; exit 2; fi'"
      }
    ]
  }
}

Rules say “please don’t.” Hooks say “you can’t.”


FAQ

Which agent runtime should I start with?

Claude Code CLI with XcodeBuildMCP. It has the deepest MCP integration, the most mature hook system, and the 1M context window (Opus 4.6) that can hold entire iOS projects in working memory. Start here, then add Codex for review and Xcode native agents for quick inline edits as your workflow matures.

Do I need both MCP servers?

For most developers, XcodeBuildMCP alone covers 90% of needs (builds, tests, simulators, debugging). Add Apple’s Xcode MCP if you want documentation search, Swift REPL verification, or SwiftUI preview rendering. You can always add it later — the two servers are independent.

Can agents create a new Xcode project from scratch?

XcodeBuildMCP includes a create_project tool that scaffolds new Xcode projects. However, for production apps, I recommend creating the project in Xcode (to get signing, capabilities, and target configuration correct) and then using agents for all code implementation. The 5 minutes you spend in Xcode’s new project wizard saves hours of agent-generated project configuration issues.

How do agents handle Swift Package Manager dependencies?

Well. Package.swift is a standard Swift file that agents can read and edit reliably. Adding dependencies, updating version ranges, and configuring targets all work. The limitation is .xcodeproj-based dependency management (Xcode’s package resolution UI) — that is Xcode-managed and should not be agent-edited.

Can agents submit to the App Store?

No. App Store submission involves Xcode’s Organizer, provisioning profiles, screenshots, metadata, and the App Store Connect portal. None of these are accessible via MCP or command-line tools in a way that agents can meaningfully operate. Agents handle everything up to the archive — implementation, testing, bug fixing, and documentation. The last mile of submission remains human-operated.

However, agents can help with App Store metadata. Ask the agent to write the app description, keywords, and what’s-new text based on the latest changes. This is text generation work where agents excel.

How do I handle secrets and API keys in agent-assisted iOS development?

Never commit secrets. For iOS apps that connect to backend APIs:

  1. Use .xcconfig files for environment-specific configuration
  2. Add .xcconfig files to .gitignore
  3. Reference config values via Info.plist build settings
  4. Document the required secrets in CLAUDE.md without including the actual values
## Configuration

API base URL and keys are in `Config.xcconfig` (not committed).
Required keys:
- `API_BASE_URL` — Backend server URL
- `API_KEY` — Authentication token

Create `Config.xcconfig` from `Config.xcconfig.template`.

The agent knows the keys exist and where they are used, but never sees the actual values.

What about SwiftUI animations — can agents write those?

Agents write animation code well syntactically but cannot verify the result visually. Simple animations (.animation(.spring()), .transition(.slide), withAnimation { }) produce correct results. Complex, multi-step animations with precise timing require visual iteration that agents cannot do.

Effective: “Add a spring animation when the timer transitions between states.”

Ineffective: “Make the timer animation feel satisfying.” (Subjective, requires visual tuning.)

How do agents handle error handling patterns?

Very well. Agents understand Swift’s do/catch, Result, and async throws patterns:

Implement error handling for the HealthKit authorization flow:
1. Check HKHealthStore.isHealthDataAvailable()  show alert if not
2. Request authorization  handle denial gracefully
3. On write failure  retry once, then show error
4. All errors should be user-facing with localized descriptions

Agents produce structured error handling with proper user-facing messages. They sometimes over-handle errors (catching exceptions that should propagate), so review the catch blocks.

Can I use agents for accessibility implementation?

Partially. Agents add accessibility labels, hints, and traits correctly:

Add accessibility labels to all interactive elements in TimerView:
- Timer display: current time remaining
- Start/Pause button: current state and action
- Reset button: "Reset timer"
- Duration picker: selected duration

What agents cannot do: verify that VoiceOver navigation order is correct, test Dynamic Type scaling, or evaluate color contrast ratios. Use Xcode’s Accessibility Inspector for verification.

How do agents handle Core Data migration (if not using SwiftData)?

Agents write Core Data migration mappings and model versions, but the manual Xcode steps (creating new model versions, selecting the current version) cannot be automated. If you are still on Core Data rather than SwiftData, document the model version history in CLAUDE.md:

## Core Data Model Versions
- V1: Initial (GroceryList, GroceryItem)
- V2: Added Category model (current)
- Migration: Lightweight automatic for V1→V2

How do agents handle SwiftUI previews?

Two ways: 1. Apple Xcode MCP’s RenderPreview tool renders previews headlessly and returns the result. The agent can verify a preview compiles and renders without errors, but it cannot evaluate visual correctness. 2. Build-based verification via build_sim confirms that preview providers compile. If a preview crashes at runtime, the build still succeeds — the crash only manifests when Xcode attempts to render the preview.

For visual verification of previews, you still need Xcode open.

What about visionOS and Apple Vision Pro?

The same patterns apply. XcodeBuildMCP supports visionOS simulators, and the architectural patterns (@Observable, NavigationStack, SwiftData) are identical. RealityKit-specific code (3D content, immersive spaces, hand tracking) follows the same limitations as Metal — agents can write correct code but cannot verify spatial output.

How large can a project be before agents struggle?

Context window size is the limiting factor. With Opus 4.6’s 1M token window, Claude Code can hold approximately 50-70 Swift files in active working memory simultaneously. For larger projects, the agent uses file search and selective reading to work with subsets of the codebase. Projects with 100+ files work fine — the agent just reads files on demand rather than holding everything in context.

The practical limit is not file count but codebase coherence. A well-documented 200-file project with a detailed CLAUDE.md produces better results than an undocumented 30-file project.

Do I need to know Swift to use agents for iOS development?

You need to be able to review agent output and make architectural decisions. You do not need to write every line yourself, but you need to understand Swift well enough to catch when the agent makes incorrect choices — especially around concurrency, memory management, and framework-specific patterns. An agent is a 10x amplifier of your existing skill, not a replacement for it.

How do agents handle merge conflicts in Swift files?

Agents resolve merge conflicts in Swift source files reliably. The standard conflict markers (<<<<<<<, =======, >>>>>>>) are well-understood by all agent runtimes. However, merge conflicts in .pbxproj files remain a manual resolution task — do not ask agents to resolve .pbxproj conflicts.

What is the cost of running agents for iOS development?

With Anthropic’s Max plan (Opus 4.6, 1M context), a typical iOS development session runs 30-120 minutes and processes 200K-800K tokens. MCP tool calls add minimal overhead (structured JSON responses are token-efficient compared to raw build output). The cost is comparable to running Claude Code for any other codebase — iOS development is not meaningfully more or less expensive than web development.

Can I use agents with UIKit projects?

Yes, but agents are more effective with SwiftUI. UIKit requires more boilerplate, has less declarative structure, and often involves Interface Builder files that agents cannot edit. If you have a UIKit project, consider using agents for the model layer and business logic while handling UI manually, or incrementally migrating views to SwiftUI.

How do agents handle localization?

Agents create and edit .xcstrings (Xcode string catalog) files effectively. They can add new localization keys, provide translations, and maintain consistency across languages. The structured JSON format of .xcstrings files is agent-friendly. For .strings files (legacy format), agents also perform well — the key-value format is straightforward.


Common Agent Mistakes in iOS (and How to Prevent Them)

These are the recurring errors I have observed across thousands of agent interactions on 8 iOS projects. Each one has a prevention strategy.

Mistake 1: Mixing Observable Patterns

What happens: The agent uses @Observable in one file and ObservableObject in another, or adds @Observable to a @Model class (which is already Observable).

Prevention: Explicit rules in CLAUDE.md:

- NEVER use ObservableObject — use @Observable
- NEVER add @Observable to @Model classes (already Observable)
- NEVER use @StateObject — use @State with @Observable
- NEVER use @ObservedObject — access @Observable properties directly

Mistake 2: Creating Retain Cycles in Closures

What happens: The agent creates closures that capture self strongly, especially in Timer.publish, NotificationCenter, and completion handlers.

Prevention: Include a closure pattern in CLAUDE.md:

## Closure Pattern
- Timer callbacks: use `[weak self]` and guard
- NotificationCenter observers: store in `Set<AnyCancellable>` and use `[weak self]`
- Completion handlers: use `[weak self]` for any closure stored beyond the call site

Mistake 3: Ignoring @MainActor Requirements

What happens: The agent creates @Observable classes without @MainActor isolation, causing Swift 6.2 concurrency warnings or runtime crashes when UI updates happen off the main thread.

Prevention:

## Concurrency Rule
ALL @Observable classes MUST be @MainActor:
```swift
@Observable
@MainActor
final class SomeManager { }
### Mistake 4: Using NavigationLink with Destination Closure

**What happens:** The agent uses the deprecated `NavigationLink(destination:label:)` instead of the type-safe `NavigationLink(value:)` + `.navigationDestination(for:)` pattern.

**Prevention:**
```markdown
## Navigation Pattern
ALWAYS use value-based navigation:
```swift
NavigationLink(value: item) { ItemRow(item: item) }
.navigationDestination(for: Item.self) { ItemDetailView(item: $0) }

NEVER use: NavigationLink(destination: ItemDetailView(item: item)) { }

### Mistake 5: Hardcoding Simulator Names

**What happens:** The agent writes build commands with specific simulator names ("iPhone 16 Pro") that may not exist on your system.

**Prevention:** MCP handles this  `list_sims` discovers available simulators. In CLAUDE.md:
```markdown
## Simulators
Do NOT hardcode simulator names. Use `list_sims` MCP tool to discover
available devices, then `boot_sim` with the discovered device ID.

Mistake 6: Creating Files in Wrong Directories

What happens: The agent creates a new view file in the project root instead of the Views/ subdirectory, or puts a model in the wrong group.

Prevention: The file structure annotations in CLAUDE.md guide placement. Additionally:

## File Placement Rules
- Views → `AppName/Views/`
- Models → `AppName/Models/`
- Managers → `AppName/Managers/`
- Extensions → `AppName/Extensions/`
- Tests → `AppNameTests/`

Mistake 7: Not Handling Platform Availability

What happens: The agent uses HealthKit in shared code that compiles for tvOS (where HealthKit is unavailable), or uses ActivityKit in watchOS code.

Prevention:

## Platform Guards
- HealthKit: `#if canImport(HealthKit)` (unavailable on tvOS)
- ActivityKit: `#if canImport(ActivityKit)` (iOS only)
- WatchKit: `#if os(watchOS)`
- UIKit haptics: `#if os(iOS)` (unavailable on tvOS, watchOS uses WKHaptic)

Mistake 8: Over-Engineering Simple Features

What happens: The agent creates a protocol, a protocol extension, a concrete implementation, a factory, and a dependency injection container for what should be a 20-line utility function.

Prevention: Include a simplicity principle:

## Architecture Principle
Prefer the simplest solution that handles the requirements.
- Direct implementation over protocol abstraction (unless you have 2+ conforming types)
- Concrete types over generics (unless reuse is proven)
- Extensions on existing types over new wrapper types

The Honest Assessment

After shipping 8 iOS apps with AI agents, here is the summary:

Agents transformed: Implementation speed. What took days takes hours. SwiftUI views, SwiftData models, unit tests, refactoring — these are now primarily agent-produced and human-reviewed.

Agents did not transform: Architectural decisions, visual design, performance optimization, or App Store submission. These remain human-driven.

The multiplier is real but bounded. I estimate 3-5x productivity improvement for well-documented projects with proper MCP and hook configuration. Undocumented projects with no hooks see maybe 1.5-2x improvement — the agent spends too much time guessing instead of building.

The investment that pays off: Time spent on CLAUDE.md, hooks, and MCP configuration. Every hour of setup saves many hours of correcting agent mistakes. The configuration is the product — the agent is the execution engine.

What surprised me: How much the MCP servers changed the dynamic. Before MCP, agents were sophisticated text editors that happened to understand Swift. After MCP, they are development partners that write, build, test, debug, and iterate. The structured feedback loop is the difference between an agent that writes code and one that ships code.

What I would tell past-me: Start with the smallest app (Reps, 14 files), get the MCP and hook setup right, write a thorough CLAUDE.md, and then scale the patterns to larger projects. Do not start with the 63-file multi-platform app. The infrastructure investment is the same regardless of project size — do it once on a small project, then copy to everything else.

The future: Xcode 26.3’s native agent integration is the beginning, not the end. Apple shipping MCP support means the toolchain is moving toward agent-first development. The developers who invest in agent-compatible project structures now — clean CLAUDE.md files, testable architectures, automated hooks — will compound that investment as the tools improve.


Quick Reference Card

Installation (One-Time Setup)

# XcodeBuildMCP (59 tools)
claude mcp add XcodeBuildMCP -s user \
  -e XCODEBUILDMCP_SENTRY_DISABLED=true \
  -- npx -y xcodebuildmcp@latest mcp

# Apple Xcode MCP (20 tools)
claude mcp add --transport stdio xcode -s user -- xcrun mcpbridge

# Codex MCP setup
codex mcp add xcode -- xcrun mcpbridge

# Verify
claude mcp list

Essential CLAUDE.md Sections

1. Project identity (bundle ID, target OS, architecture)
2. File structure with annotations
3. Build and test commands
4. Key patterns and rules
5. Prohibitions (NEVER touch .pbxproj)
6. Framework-specific context

Essential Hooks

{
  "PreToolUse": [{ "matcher": "Edit|Write", "command": "block .pbxproj" }],
  "PostToolUse": [{ "matcher": "Edit|Write", "command": "swiftformat" }]
}

Architecture Rules

@Observable         (not ObservableObject)
NavigationStack     (not NavigationView)
@State              (not @StateObject)
SwiftData @Model    (not Core Data)
async/await         (not completion handlers)
@MainActor          (on all Observable classes)
.glassEffect()      (Liquid Glass, iOS 26+)

MCP Tool Priorities

Build:     build_sim          (not xcodebuild via Bash)
Test:      test_sim           (not xcodebuild test via Bash)
Sim:       list_sims/boot_sim (not xcrun simctl via Bash)
Docs:      DocumentationSearch (not WebSearch)
REPL:      ExecuteSnippet     (not swift via Bash)

Changelog

Date Changes
2026-04-13 Initial publication. 8 apps, 3 runtimes, MCP setup, CLAUDE.md patterns, hooks, case studies.

References


  1. XcodeBuildMCP includes Sentry telemetry by default. The project’s privacy documentation details what gets sent: error messages, stack traces, and in some cases file paths. The XCODEBUILDMCP_SENTRY_DISABLED=true env var opts out entirely. 

  2. Anthropic, “Model Context Protocol Specification,” modelcontextprotocol.io/specification. The MCP specification defines the JSON-RPC transport, tool discovery, and resource protocol that both XcodeBuildMCP and Apple’s Xcode MCP implement. 

  3. XcodeBuildMCP, github.com/getsentry/XcodeBuildMCP. Open source, maintained by Sentry. 59 tools across simulator, device, debugging, and UI automation workflows. Semantic versioning with changelogs. 

  4. Apple introduced the Xcode MCP server as part of the Xcode 26.3 intelligent developer tools initiative, positioning MCP as the interface layer between AI coding assistants and the Xcode toolchain. See Xcode Release Notes for official documentation. 

  5. Rudrank Riyam, “Exploring Xcode Using MCP Tools,” rudrank.com/exploring-xcode-using-mcp-tools-cursor-external-clients, 2026. Independent confirmation of Apple’s MCP tool count, XPC dependency, and documentation search capabilities. 

  6. Jimenez, C.E., Yang, J., Wettig, A., et al., “SWE-bench: Can Language Models Resolve Real-World GitHub Issues?” ICLR 2024. arxiv.org/abs/2310.06770. Agents with structured tool access significantly outperformed agents limited to unstructured shell commands. The finding validates structured MCP interfaces for agent effectiveness. 

  7. Claude Code CLI documentation, code.claude.com. Hook system, MCP configuration, subagent delegation, and agent definitions. 

  8. SwiftFormat, github.com/nicklockwood/SwiftFormat. The Swift formatting tool used in PostToolUse hooks for consistent code style. 

  9. XcodeBuildMCP official site, xcodebuildmcp.com. Confirms 59 MCP tools via CLI output example. Tool categories: simulator, device, debugging, and UI automation. Install via Homebrew or npx. 

  10. Swiftjective-C, “Agentic Coding in Xcode 26.3 with Claude Code and Codex,” swiftjectivec.com, February 2026. Confirms Xcode 26.3 ships with native Claude Agent and Codex runtime support via Settings > Intelligence. 20 MCP tools exposed via xcrun mcpbridge

  11. Blake Crosley, “Two MCP Servers Made Claude Code an iOS Build System,” blakecrosley.com/blog/xcode-mcp-claude-code, February 2026. Setup walkthrough and real-world results from the same author’s iOS development workflow. 

NORMAL ios-agent-development.md EOF