Home/Features/ios-dev
plugin · 34 skills

ios-dev

Build iOS apps, screenshot remotely, release to App Store.

Install → View source ↗
34
skills
6
commands
1
config file

01 Overview

The ios-dev plugin provides a complete iOS development loop without leaving the chat. Build for simulator or device, capture screenshots automatically, and deliver them to your iPhone via a hybrid iMessage+iCloud channel so you see the result within seconds. The release skill automates versioning, archiving, validation, and App Store Connect upload. Additional skills cover XcodeGen setup, alternate app icons, biometric locks, Swift 6 concurrency migration, and test-target scaffolding. Every skill reads configuration from .claude/app.yml (auto-scaffolded via /ios-init), which encodes your app's scheme, bundle ID, team ID, and URL scheme once—no copy-pasting bundle IDs into invocations.

02 Problem & why it's needed

The friction

iOS development requires constant context-switching between simulator builds, screenshots to see progress, device builds for testing, and TestFlight/App Store releases—each step is manual and error-prone. The remote feedback loop is especially broken: you can't embed images in Claude's Remote Control mobile interface, so you can't see the result of a UI fix without physically walking to the machine. The release process has specific gotchas (provisioning profiles, build numbers, archiving, validation, App Store rejection codes) that are non-obvious and require frequent re-learning.

Why it mattersiOS developers working remotely (the most common setup) need fast visual feedback on UI changes—plain text descriptions don't substitute for screenshots. The plugin eliminates the manual, context-heavy steps in building, testing, and releasing iOS apps.

03 Install

Via Claude Code marketplace

/plugin marketplace add abhijitbansal/claude-skills
/plugin install ios-dev@claude-skills

Scaffold app config in your iOS repo

/ios-init

Generates .claude/app.yml (auto-detects scheme, bundle_id from project.yml or xcodebuild; you fill in team_id and url_scheme)

04 Usability guide

Every capability, how to invoke it, and a concrete example.

ios-build: Compile for simulator or device

Wrap xcodebuild with device detection, provisioning-profile team ID resolution, and devicectl install. Use when you want to build without taking a screenshot.

./build.sh # simulator (iPhone 17 Pro default)
./build.sh -s "iPhone 16 Pro" # specific simulator
./build.sh -d # real device (connected iPhone)
./build.sh -d --debug # inspect without building

/preview command: Build and screenshot on-demand

Build the app for the booted simulator, launch it (optionally deep-linking to a specific screen), capture a screenshot, and automatically deliver it to your iPhone via iMessage ping + iCloud Drive. This is the load-bearing command for remote development—you see the result of a change within seconds.

/preview # cold-start home screen
/preview scan # deep-link to scan route (if handleDeepLink implements it)
/preview doc?path=path/to/doc.pdf # deep-link with query parameter
/preview --no-build # skip rebuild, use last build (fast)

/fix command: Apply a UI change and prove it worked

The remote-first workflow: describe what to fix, Claude edits the SwiftUI source, rebuilds, screenshots, and delivers the result to your phone in one command. You stay in chat—no walking to the machine, no manual screenshot steps.

/fix the home row padding looks too loose, tighten it by 4pt

/ios-init command: Scaffold .claude/app.yml

One-time setup that detects your app's scheme, bundle ID, and team ID from project.yml or xcodebuild, then prompts you for anything it couldn't find (typically team_id and url_scheme). After this, all other commands work without configuration.

/ios-init # auto-detect and write .claude/app.yml
/ios-init --force # overwrite an existing config

app-preview skill: Multi-screen capture from a description

When you say a UI concern spans multiple screens (e.g., 'the home-row spacing looks off on the doc screen too'), this skill builds once and captures all affected screens with deep-links, organizes them by git branch in your phone's Files app, and appends them to a per-branch MANIFEST.md so you can flip between branches and see which screenshots belong where.

scripts/bundle.sh \ --description "home-row spacing looks off on the doc screen" \ --screen home \ --screen doc:Receipts/2026-receipt.pdf

release skill: Automated TestFlight and App Store release

Handle version bumps, archiving, IPA export, App Store validation, and upload via CLI. Two modes: testflight (everyday) bumps build number; appstore (milestone) bumps marketing version, asks for changelog, validates widget entitlements, and prints a web-UI checklist for the rest. Refuses to run on dirty trees and asks for explicit confirmation before the irreversible upload step.

release testflight # bump build, archive, validate, upload to TestFlight
release appstore # bump version, changelog, archive, validate, upload, tag
release testflight --skip-tests # skip simulator sanity build
release appstore --force # bypass pre-flight checks

demo-recording skill: Repeatable video/GIF from XCUITests

Record deterministic demo videos without hand-driving the simulator. Write a paced XCUITest class (Thread.sleep + lenient guards) gated by DEMO_RECORDING=1 env var, then this skill builds once, records via simctl recordVideo, and converts to GIF with ffmpeg (with CoreText caption fallback if ffmpeg lacks libfreetype).

# In your XCUITest: try XCTSkipUnless(ProcessInfo.processInfo.environment["DEMO_RECORDING"] == "1") # Then skill converts *.mov → *.gif and trims launch dead-time

alternate-app-icons skill: User-selectable app icons for XcodeGen

Add alternate app icons to an XcodeGen project without hand-editing Info.plist. Handles the ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES build setting, the opaque-RGB App Store requirement (alpha → blank tile), separate downscaled imagesets for picker UI, and setAlternateIconName() switching.

# Add to project.yml settings: ASSTCATALOG_COMPILER_ALTERNATE_APPICON_NAMES: AppIconClassic # In code: try await UIApplication.shared.setAlternateIconName("AppIconClassic")

biometric-applock skill: Face ID lock with four bypass-pitfall fixes

Implement a robust SwiftUI biometric app lock. This skill teaches the four real bypass windows in naive @MainActor + .overlay patterns (app-switcher snapshot leak, presentation layer z-order, toggle bypass, background vs. inactive scenePhase). Covers LAContext evaluation, @fullScreenCover wiring, and external entry-point gating (App Intents, deep links, notification handlers).

ContentView() .fullScreenCover(isPresented: Binding(get: { appLock.isLocked }, set: { _ in })) { AppLockOverlayView().interactiveDismissDisabled() } .onAppear { appLock.bootstrap() }

swift6-mainactor-compile-fixes skill: Fix MainActor isolation compile errors

When your project sets SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor (Swift 6.2 / Xcode 26), pure-compute types (exporters, builders, parsers) emit 'cannot be called from outside of the actor' errors, and plain zero-state structs/enums fail to compile off-main purely from synthesized Decodable/Equatable/Hashable conformance. This skill teaches the honest fix: mark those types nonisolated at the type declaration, then cascade through flagged callees. Forbids @unchecked Sendable and @preconcurrency band-aids.

# Before: struct PDFExporter { … } // implicitly @MainActor, but runs off-main # After: nonisolated struct PDFExporter { … } // honest about its isolation

xcode-cloud-validate skill: Debug opaque Xcode Cloud archive failures

When Xcode Cloud's 'Prepare Build for App Store Connect' fails with no detailed error, the real ITMS rejection code is buried. This skill shows how to reproduce it locally: download the ARCHIVE_EXPORT .ipa artifact, then run xcrun altool --validate-app to surface the exact ITMS error (e.g., ITMS-90098 for invalid UIRequiredDeviceCapabilities).

# After downloading ARCHIVE_EXPORT artifact: xcrun altool --validate-app \ -f App.ipa -t ios \ --apiKey KEY_ID --apiIssuer ISSUER_ID

xcodegen-test-targets skill: Add tests to XcodeGen projects

XcodeGen ships no test-target template. This skill scaffolds Swift Testing unit tests and XCUITest targets with the exact TEST_HOST / BUNDLE_LOADER shape, wires them into the scheme's test action (tests silently don't run if omitted), and warns about regenerate-before-testing gotchas.

# In project.yml: targets: AppTests: type: bundle.unit-test sources: [AppTests] dependencies: - target: App settings: base: TEST_HOST: "$(BUILT_PRODUCTS_DIR)/App.app/App" BUNDLE_LOADER: "$(TEST_HOST)"

05 How it works under the hood

Configuration: .claude/app.yml loaded once, used everywhere

Every skill sources skills/_lib/load_app_config.sh, which reads .claude/app.yml (your app's name, bundle_id, scheme, team_id, url_scheme). Scaffolded by /ios-init, it auto-detects what it can from project.yml or xcodebuild, then prompts you for the rest. Once written, no skill needs configuration—they all see the same canonical values.

Build wrapper: ./build.sh handles device detection and signing

Rather than invoking xcodebuild directly, ios-build runs your app's ./build.sh. The wrapper handles xcodegen regeneration (sources are globbed at generate time), detects connected iPhone UDIDs via xcrun device discover, resolves team ID from provisioning profiles (not the keychain, which can be stale), and uses devicectl install for device deployment. Logs are tee'd so build errors surface verbatim.

Screenshot delivery: Hybrid iMessage + iCloud Drive channel

Claude's Remote Control UI cannot embed images in chat (verified Anthropic limitation). Instead, app-preview uses a two-channel workaround: (1) AppleScript text ping to self via iMessage (works, unlike AppleScript file attachments which Apple silently rejects at the server level), which triggers a push notification on your iPhone; (2) actual PNG copied to ~/Library/Mobile Documents/com~apple~CloudDocs/$(basename APP_PREVIEW_ROOT)/<branch>/, which syncs to your Files app in 5–30 seconds. Two taps on your phone: notification → open Files → tap the image.

Screenshot organization: Per-branch folders, stable across branch switches

All screenshots are organized by git branch name (slashes converted to -- for iOS Files compatibility). When you switch branches, the next screenshot lands in a new folder; old screenshots stay put. This makes it trivial to flip between branches without screenshots getting mixed up. Detached HEAD falls back to detached-<shortsha>.

Deep-link routing: xcrun simctl openurl → app's handleDeepLink()

To navigate to a specific screen without tapping, app-preview runs xcrun simctl openurl booted URL_SCHEME://route. Your app must implement handleDeepLink() to parse the URL and navigate. The skill only accepts routes it knows exist (verified against your app's source). Example: scan posts a .snapDocTriggerScan notification (Paperix shape), or doc?path=Receipts/2026.pdf opens a document detail screen.

Release pipeline: 9-stage Fastlane-first pipeline with gated pre-flight

After stage 0 validates .claude/app.yml (schema v2), the release skill runs nine gated stages: (1) preflight.sh — tree-clean, signing identity, fonts, usage strings, encryption flag, required capabilities, App Group entitlement parity across extensions, an NFC entitlement check, an iPad orientation check, a WARN-only MainActor runtime-trap audit (heavy work in App.init/.task/onAppear), and ASC-metadata + in-app whatsnew checks (two independent surfaces — see skill release-inapp-vs-asc-whatsnew-surfaces); (2) version bump via bump_version.sh; (3) release notes from Release-Note: commit trailers; (4) local sim build + test; (5) archive/export — Fastlane gym when fastlane/Fastfile exists (created by /ios-scaffold), else raw xcodebuild; (6) validate + upload — Fastlane pilot/deliver falling back to altool, gated on typing the literal word "upload"; (7) annotated git tag; (8) site deploy via /site deploy (appstore mode); (9) the remaining App Store Connect web-UI checklist. Any FAIL gate in stage 1 stops the run unless --force.

Config schema v2: one app.yml every skill reads

.claude/app.yml carries a top-level schema_version (1 or 2); every skill sources skills/_lib/load_app_config.sh, which resolves it with a naive YAML reader (PyYAML if present, a stdlib regex fallback if not) and exports APP_*/LINEAR_* env vars. skills/_lib/validate_app_config.sh is the schema gate — it prints ERROR: lines for missing or TODO required keys and unsupported schema_version values, WARN: for an unversioned v1 file, and the release skill's stage 0 refuses to proceed on any ERROR. /ios-init --migrate upgrades a v1 file to v2 in place, preserving every existing value and appending only the sections it lacks.

ios-scaffold: CREATE/OK/DRIFT/SKIP idempotency

scripts/scaffold.sh renders a managed set of files (marketing listing, Fastlane Fastfile/Gemfile, ci_scripts/ci_post_clone.sh, scripts/release-hooks/, the architecture checklist, an AGENTS.md skeleton) from .claude/app.yml values, then reports each path as one of four verdicts: CREATE (written because missing), OK (matches the current template), DRIFT (exists but differs — left as-is, never overwritten), or SKIP. scaffold.sh --check only reports and exits 1 when anything would CREATE or is in DRIFT, so it doubles as a CI gate. DRIFTs are walked with the user one file at a time — adopt the template, keep the divergence, or merge by hand.

Xcode Cloud ci_post_clone: the four-rule contract

An XcodeGen repo has no .xcodeproj in git, so ci_scripts/ci_post_clone.sh must: (1) materialize the gitignored project by running every local generation step in the same order as build.sh, ending in xcodegen generate; (2) mirror any new local generation step into the same script in the same change — the invisible-drift failure mode; (3) pin SPM by copying a committed root Package.resolved into <App>.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/, since Xcode Cloud disables automatic resolution; (4) stay brew + stdlib only — no gems, no tokens, no network beyond brew install xcodegen. /ios-scaffold places the canonical template; skill xcode-cloud-validate covers pre-push validation and skill xcode-cloud-post-clone-contract has the full recipe plus the PR-check/tag-release workflow pair.

Version management: Automatic bump, committed immediately

Stage 3 increments CURRENT_PROJECT_VERSION in project.yml, regenerates via xcodegen, and immediately commits (so the tag at stage 10 points to a named state). Stage 2 (appstore mode) asks for the new marketing version, validates semver format, edits project.yml, and regenerates. Both changes are explicit and tracked in git.

Validation and upload: altool drives App Store Connect API

Stage 8 runs xcrun altool --validate-app against the .ipa to surface ITMS rejection codes before upload. Stage 9 runs xcrun altool --upload-app only after explicit 'upload' confirmation (irreversible step). Both commands auto-locate the API key at ~/.app-store-connect/AuthKey_<KEY_ID>.p8 if the filename is exact. altool's error messages are clear (e.g., 'Redundant Binary Upload' means the build number was already uploaded).

Pure-compute type isolation: nonisolated at the type level

Under Swift 6 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, all unannotated types are implicitly @MainActor. The swift6-mainactor-compile-fixes skill teaches marking pure-compute types (exporters, builders, parsers) with nonisolated struct/enum X at the type declaration. The compiler cascades: helpers and models that the nonisolated code calls get flagged next, and you mark them too. The fix is local—no await hops, no @unchecked band-aids, no mutation of main-actor state.

Demo recording: Paced XCUITests, gated by env var, converted to GIF

Write an XCUITest class with Thread.sleep pacing and lenient guards (waitForExistence, if-checks, continueAfterFailure=true), gated by ProcessInfo.processInfo.environment["DEMO_RECORDING"] == "1". The skill runs xcodebuild build-for-testing, then spawns simctl recordVideo in the background, runs the test with TEST_RUNNER_DEMO_RECORDING=1 xcodebuild test-without-building, and kills the recorder on SIGINT to finalize the .mov. Then ffmpeg converts to GIF with fps=8, scale, and palettegen/paletteuse. If ffmpeg lacks drawtext (no libfreetype), the skill generates a CoreText caption PNG and composites it with the overlay filter.

06 What's inside

Skills

ios-buildBuild the app for simulator (default iPhone 17 Pro), a specific simulator with -s, or a real device with -d. Wraps build.sh to handle xcodegen regen, UDID detection, provisioning-profile team ID resolution, and devicectl install.
app-previewBuild, launch, optionally deep-link, screenshot, and deliver to iPhone via iMessage+iCloud. Organizes screenshots by git branch. Includes bundle.sh for text-driven multi-screen capture (give a description, list screens, get them all captured and delivered).
releaseGated pre-flight (compliance strings, entitlement parity, MainActor runtime-trap audit) + Fastlane gym/pilot/deliver with xcodebuild/altool fallback. Two modes—testflight (bump build number) and appstore (bump marketing version, changelog, tag, site deploy). Nine gated stages after config validation, with recovery moves and explicit confirmation before irreversible upload.
demo-recordingRecord repeatable demo videos/GIFs from paced XCUITests (gated by DEMO_RECORDING=1 env var). Build once, run test with recorder, finalize on SIGINT, convert to GIF with ffmpeg (with CoreText caption fallback if libfreetype is missing).
alternate-app-iconsAdd user-selectable alternate app icons to XcodeGen projects without hand-editing Info.plist. Handles ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES, opaque-RGB App Store requirement, downscaled thumbnail imagesets, and setAlternateIconName() switching.
biometric-applockImplement Face ID / passcode app lock in SwiftUI. Teaches four real bypass pitfalls: app-switcher snapshot leak (init not onAppear), presentation layer z-order (fullScreenCover not overlay), toggle bypass (auth gate to disable), and scenePhase gating (background only, gate external entry points).
swift6-mainactor-compile-fixesResolve 'main actor-isolated X cannot be called from outside of the actor' errors and 'main actor-isolated conformance of X to Decodable cannot be used in nonisolated context' under SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor. Mark pure-compute types nonisolated at the type level; cascade through callees; forbid @unchecked Sendable and @preconcurrency band-aids.
xcode-cloud-validateDebug opaque Xcode Cloud archive failures by downloading the ARCHIVE_EXPORT .ipa artifact and running xcrun altool --validate-app locally to surface the exact ITMS error codes. Includes ES256 JWT shape for ASC API if needed.
xcodegen-test-targetsAdd Swift Testing unit tests and XCUITest targets to XcodeGen projects. Scaffolds TEST_HOST / BUNDLE_LOADER shape, wires targets into the scheme's test action, and warns about regenerate-before-testing and @testable actor-isolation gotchas.
ios-scaffoldIdempotent repo standardizer: marketing copy home, Fastlane files, ci_post_clone.sh, release-hooks dir, architecture checklist, AGENTS.md skeleton. Creates what's missing, reports drift, never overwrites.
site-pages-deploy-kitThe site standard (floorprint model): site/ source in the app repo → split-repo public GitHub Pages, subtree force-push over an SSH deploy key, og/CSP/favicon lint, skeleton + runbook.
xcode-cloud-post-clone-contractThe four-rule ci_scripts/ci_post_clone.sh contract (materialize gitignored .xcodeproj, mirror local generation, pin Package.resolved, brew-only) plus the PR-check/tag-release workflow recipe.
site-og-favicon-verifyFixes Teams/iMessage/Slack unfurl showing no image or wrong size, favicon missing, fonts blocked by CSP: absolute og:image + true dimensions, self-hosted fonts + strict CSP, complete favicon set.
mainactor-launch-watchdog-auditDiagnoses launch watchdog 0x8BADF00D SIGKILL + boot-loop crashes from heavy work implicitly running on MainActor at launch; off-main idioms + idempotent-retry rule.
mainactor-runtime-isolation-trapDiagnoses brk 1 crashes on SwiftUI's AsyncRenderer thread from @MainActor closures captured by UIKit (dynamic color/image providers) and invoked off-main; includes re-entrancy guard patterns.
swiftdata-cloudkit-model-rulesCloudKit-safe SwiftData: explicit cloudKitDatabase config, throwing container factory with fallback, single-side @Relationship inverse, avoiding NSManagedObject-reserved names, centralized schema.
widget-appgroup-snapshot-bridgeApp→widget snapshot bridge over an App Group: one Codable DTO, atomic writes, backfill-on-launch, and the transient-empty-clobber + App-Lock double-redaction invariants that shipped as bugs before.
file-handoff-inbox-backstopApp-Group share/action-extension handoff inbox with a per-batch attempt-cap + quarantine so one poison shared file can't boot-loop the host app.
deep-link-resolver-applock-pathtraversalOne pure resolver for all deep-link URLs; drops (not defers) links while App-Lock is engaged; validates against path traversal.
vision-layout-ocr-groundingGrounds on-device AI on Vision-layout text instead of PDFDocument.string (which collapses multi-column layouts and causes confabulation); versioned sidecar; verify on the cold path.
ondevice-generable-anti-hallucinationApple @Generable patterns: flat schemas only (nested hangs generation on iOS 26), verbatim-quote pinning, clipping grounding text to the model's context window.
scan-crash-recovery-storeRecovers long RoomPlan/ARKit captures: persist the processed result before the hang-prone build step; harden against partial/corrupt recovery files; crash marker.
scan-capture-quality-gatesSoft variance-of-Laplacian sharpness gate (not a hard reject) plus scan auto-naming discipline to stop OCR prose becoming an item name.
avfoundation-capture-delivery-watchdogCamera capture "gets stuck" with no error — AVCapturePhotoCaptureDelegate callback never fires after a session mid-teardown/re-mount race; add a timeout watchdog around the one-shot delegate call.
ios26-toolbar-leading-title-truncationA SwiftUI title placed in ToolbarItem(.topBarLeading) renders on iOS 26 as a single letter + ellipsis inside a glassy Liquid Glass circle instead of full text.
swiftdata-inmemory-test-harnessFixes SwiftData test-runner crashes, "Batch delete failed due to mandatory OTO nullify inverse" failures, and cross-suite races when store-backed test suites run together.
vision-barcode-cidetector-fallbackVNDetectBarcodesRequest returns zero results for a valid QR/barcode — Vision's ML context fails to init, reliably on Simulator — falls back to CIDetector so decode tests stay green in CI.
swiftui-tabbar-swipe-nav-tradeoffTabView won't give a labeled bottom bar AND swipe-between-tabs at once; documents the custom-pager tradeoff and its race with programmatic tab selection from deep links/NFC/Siri.
query-derived-typeahead-vocabularyType-ahead suggestion chips derived from the persistent store instead of a stale hardcoded list, with filtering logic shared and testable across add/edit/bulk screens.
github-pages-flat-deploy-subdir-404Fixes a GitHub Pages deploy that's green in CI but 404s a subdirectory doc page, because a flat `cp docs/*.html _site/` plus top-level link rewrite never descends into subdirs.
pillow-favicon-set-no-rasterizerGenerates favicon.ico / apple-touch-icon.png / og-card.png from a simple geometric+glyph SVG mark with Pillow, when rsvg-convert/ImageMagick/cairosvg/Inkscape aren't available.
legal-pages-css-scoping-bleedFixes privacy/support/legal pages broken by hero-only CSS rules bleeding in from a shared homepage stylesheet, plus nav/footer link drift from hand-duplicated per-page markup.
parallel-ios-agent-fixes-single-simFans subagents out to fix independent code-review findings or plan tasks in an iOS repo without them stomping the same working tree or racing xcodebuild test runs on one simulator.
release-inapp-vs-asc-whatsnew-surfacesAn app can have both an in-app What's New/changelog screen (compiled into the binary) and App Store Connect's What's New/What to Test metadata (a manual paste) — two independent surfaces that silently drift; gates the release preflight on both.

Commands

/previewBuild, screenshot, and deliver to iPhone. Optional args: deep-link route (e.g., scan, doc?path=...), or --no-build to skip rebuild.
/fixApply a UI fix (description in args), rebuild, screenshot, deliver to iPhone in one command. Closes the remote feedback loop.
/ios-initScaffold .claude/app.yml by auto-detecting scheme and bundle_id from project.yml or xcodebuild, then prompting for team_id and url_scheme. Append --force to overwrite.
/ios-scaffoldRun the repo standardizer and walk drift items one by one.
/releasetestflight or appstore (--dry-run supported): gated Fastlane release pipeline, stops at every failing gate.
/sitecreate/deploy/verify the marketing site per the standard.

Explore the other plugins

Browse the full skills & tools catalog →