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
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.
03 Install
Via Claude Code marketplace
/plugin marketplace add abhijitbansal/claude-skills/plugin install ios-dev@claude-skillsScaffold app config in your iOS repo
/ios-initGenerates .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 configapp-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.
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 TestFlightrelease appstore # bump version, changelog, archive, validate, upload, tagrelease testflight --skip-tests # skip simulator sanity buildrelease appstore --force # bypass pre-flight checksdemo-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).
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.
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).
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.
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).
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.
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
Commands
Explore the other plugins
Second Wind
Run Claude overnight; auto-resume after 5-hour limit resets
prompt-craft
Sharpen rough prompts into specs, suggest follow-up commands
core-workflow
Opinionated dev workflow: commit + share + team up
linear-pm
Linear issues → code, branches, PRs — autonomous end-to-end