skills/ios-simulator/SKILL.md
Manages iOS Simulator devices and tests app behavior using xcrun simctl. Covers device lifecycle (create, boot, shutdown, erase, delete), app install and launch, push notification simulation, location simulation, permission grants via privacy subcommand, deep link testing via openurl, status bar overrides, screenshot and video recording, log streaming with os_log filtering, get_app_container paths, and #if targetEnvironment(simulator) compile-time checks. Use when creating or managing simulator devices, testing push notifications without APNs, simulating GPS locations, granting or resetting privacy permissions, capturing screenshots or screen recordings from the command line, streaming device logs, debugging simulator boot failures, troubleshooting CoreSimulator issues, or checking simulator hardware limitations.
npx skillsauth add dpearson2699/swift-ios-skills ios-simulatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage iOS Simulator devices and test app behavior from the command line using xcrun simctl. Covers the full device lifecycle, app deployment, push and location simulation, permission control, screenshot and video recording, log streaming, and compile-time simulator detection.
For common subcommands, syntax, and examples, see references/simctl-commands.md.
# List all available simulators grouped by runtime
xcrun simctl list devices available
# List installed runtimes
xcrun simctl list runtimes
# List only booted devices
xcrun simctl list devices booted
# JSON output for scripting
xcrun simctl list -j devices available
Parse JSON output to find a specific device programmatically. See references/simctl-commands.md for jq parsing examples.
# Find available device types and runtimes
xcrun simctl list devicetypes
xcrun simctl list runtimes
# Create a device — returns the new UDID
xcrun simctl create "My Test Phone" "iPhone 16 Pro" "com.apple.CoreSimulator.SimRuntime.iOS-18-4"
Device types and runtime identifiers in examples throughout this skill are illustrative. Run simctl list devicetypes and simctl list runtimes to find the identifiers available on your system.
The returned UDID identifies the device for all subsequent commands. Use descriptive names to distinguish devices in simctl list output.
# Boot a specific device
xcrun simctl boot <UDID>
# Boot if needed and wait until the device is ready
xcrun simctl bootstatus <UDID> -b
# Shutdown a running device
xcrun simctl shutdown <UDID>
# Factory reset — wipes all data, keeps the device
xcrun simctl erase <UDID>
# Delete a specific device
xcrun simctl delete <UDID>
# Delete all devices not available in the current Xcode
xcrun simctl delete unavailable
# Shutdown everything
xcrun simctl shutdown all
Use booted as a UDID shorthand when exactly one simulator is running:
xcrun simctl shutdown booted
If multiple simulators are booted, booted picks one of them non-deterministically. Prefer explicit UDIDs when running parallel simulators.
In scripts and CI, use xcrun simctl bootstatus <UDID> -b before install, launch, push, or location commands. If you call simctl boot separately, follow it with xcrun simctl bootstatus <UDID> before continuing.
# Build for simulator first
xcodebuild build \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-derivedDataPath build/
# Boot and wait for SpringBoard/services before install
xcrun simctl bootstatus <UDID> -b
# Install the .app bundle
xcrun simctl install <UDID> build/Build/Products/Debug-iphonesimulator/MyApp.app
The path must point to a .app directory built for the simulator architecture, not a .ipa file.
# Launch by bundle ID
xcrun simctl launch booted com.example.MyApp
# Launch and stream stdout/stderr to the terminal
xcrun simctl launch --console booted com.example.MyApp
# Pass launch arguments
xcrun simctl launch booted com.example.MyApp --reset-onboarding -AppleLanguages "(fr)"
# Terminate a running app
xcrun simctl terminate booted com.example.MyApp
--console is useful for debugging — it shows print() and os_log output directly in the terminal.
# App bundle location
xcrun simctl get_app_container booted com.example.MyApp app
# Data container (Documents, Library, tmp)
xcrun simctl get_app_container booted com.example.MyApp data
# Shared app group container
xcrun simctl get_app_container booted com.example.MyApp group.com.example.shared
Use these paths to inspect sandboxed files, databases, or UserDefaults during debugging.
Create a JSON payload file:
{
"aps": {
"alert": {
"title": "New Message",
"body": "You have a new message from Alice"
},
"badge": 3,
"sound": "default"
},
"customKey": "customValue"
}
Send it to the Simulator:
# Send push payload from file
xcrun simctl push booted com.example.MyApp payload.json
# Pipe payload from stdin
echo '{"aps":{"alert":"Quick test"}}' | xcrun simctl push booted com.example.MyApp -
This simulates local delivery only — no APNs connection is involved. Use this to test payload handling, notification display, and notification actions. Always verify on a real device before shipping to confirm APNs delivery works end to end.
# Set a fixed coordinate (latitude, longitude)
xcrun simctl location booted set 37.3349,-122.0090
# List available predefined scenarios
xcrun simctl location booted list
# Run a predefined scenario
xcrun simctl location booted run "City Run"
# Follow custom command-line waypoints
xcrun simctl location booted start --speed=15 --interval=1 \
37.3349,-122.0090 37.3317,-122.0307
# Read waypoints from stdin, one "lat,lon" pair per line
printf "37.3349,-122.0090\n37.3317,-122.0307\n" | \
xcrun simctl location booted start --distance=100 -
# Clear the simulated location
xcrun simctl location booted clear
Use set for one coordinate, run for predefined scenario names, and start for custom waypoint routes. The command boundary matters: simctl location run accepts built-in scenario names (e.g., "City Run", "Freeway Drive"), not GPX file paths; simctl location start is the command-line path for custom coordinate waypoints. Use Xcode's Debug > Simulate Location menu for GPX-based routes.
Location simulation affects all apps using Core Location on the booted device. Clear the location when done to avoid unexpected test results.
# Grant a permission
xcrun simctl privacy booted grant photos com.example.MyApp
# Revoke a permission
xcrun simctl privacy booted revoke microphone com.example.MyApp
# Reset all permissions for the app
xcrun simctl privacy booted reset all com.example.MyApp
Common service names: photos, microphone, contacts, calendar, reminders, location, location-always, motion, siri. See references/simctl-commands.md for the full list.
Pre-granting permissions in CI avoids system permission dialogs that block automated test runs, but it can mask missing usage description keys. Keep the required Info.plist privacy strings in place and still test the normal prompt flow.
# Open a URL (triggers universal links or custom URL schemes)
xcrun simctl openurl booted "https://example.com/product/123"
# Custom URL scheme
xcrun simctl openurl booted "myapp://settings/notifications"
For universal links, the app's associated domains entitlement must be configured. The Simulator uses the apple-app-site-association file from the domain.
# Set a clean status bar for screenshots
xcrun simctl status_bar booted override \
--time "9:41" \
--batteryState charged \
--batteryLevel 100 \
--cellularMode active \
--cellularBars 4 \
--wifiBars 3 \
--operatorName ""
# Clear all overrides
xcrun simctl status_bar booted clear
Use status bar overrides to produce consistent App Store screenshots. Always clear overrides after capturing to avoid confusing other testing.
# Capture a screenshot
xcrun simctl io booted screenshot screenshot.png
# Record video (press Ctrl+C to stop)
xcrun simctl io booted recordVideo recording.mov
# Screenshot with specific display mask
xcrun simctl io booted screenshot --mask black screenshot.png
--mask options: ignored (default, no mask), alpha (transparent corners), black (black corners). Use alpha or black when capturing screenshots that show the device shape. The alpha mask is only supported for screenshots — video recording falls back to black.
Video recording continues until the process receives SIGINT (Ctrl+C). The recording is saved only after stopping — killing the process with SIGKILL loses the file.
# Stream all logs at debug level and above
xcrun simctl spawn booted log stream --level debug
# Filter by subsystem
xcrun simctl spawn booted log stream --level debug \
--predicate 'subsystem == "com.example.app"'
# Filter by subsystem and category
xcrun simctl spawn booted log stream --level debug \
--predicate 'subsystem == "com.example.app" AND category == "networking"'
# Filter by process name
xcrun simctl spawn booted log stream \
--predicate 'process == "MyApp"'
Design subsystems and categories for filterability:
import os
let networkLogger = Logger(subsystem: "com.example.app", category: "networking")
let uiLogger = Logger(subsystem: "com.example.app", category: "ui")
func fetchData() async throws -> Data {
networkLogger.debug("Starting request to /api/data")
let (data, response) = try await URLSession.shared.data(from: url)
networkLogger.info("Received \(data.count) bytes, status: \((response as? HTTPURLResponse)?.statusCode ?? 0)")
return data
}
Then filter the log stream to see only networking output:
xcrun simctl spawn booted log stream --level debug \
--predicate 'subsystem == "com.example.app" AND category == "networking"'
Use #if targetEnvironment(simulator) to exclude code that cannot run in the Simulator:
func registerForPush() {
#if targetEnvironment(simulator)
logger.info("Skipping APNs registration — running in Simulator")
#else
UIApplication.shared.registerForRemoteNotifications()
#endif
}
Runtime detection via environment variables:
var isSimulator: Bool {
ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
}
Prefer compile-time checks (#if targetEnvironment(simulator)) over runtime checks. The compiler strips excluded code entirely, preventing linker errors from unavailable symbols.
When classifying a test as Simulator-supported or hardware-required, separate "can trigger this behavior" from "can validate real-device fidelity":
simctl push, location changes, memory warnings, Face ID or Touch ID enrollment/match/nonmatch menu actions, manual iCloud sync trigger, privacy grants/resets, status bar overrides, screenshots, and video capture.Do not classify "iCloud sync" as a single hardware-only item. Split it explicitly:
| Capability | Simulator Support |
|-----------|------------------|
| APNs push delivery | No — use simctl push for local simulation |
| Performance fidelity | Relative only — Simulator is not accurate for CPU/processing performance, networking speed, graphics/Metal, memory bandwidth, frame timing, memory pressure, Jetsam, shader correctness, or thermal behavior; verify performance-sensitive work on hardware |
| Metal GPU family parity | Partial — Simulator uses the host Mac GPU, not the device GPU; some shaders and limits differ |
| Camera hardware | No — use photo library injection or mock AVCaptureSession |
| Audio input / microphone | No general app audio input; Siri can be activated from Simulator menus |
| Secure Enclave | No — kSecAttrTokenIDSecureEnclave operations fail |
| App Attest (DCAppAttestService) | No — isSupported returns false |
| DockKit motor control | No — no physical accessory connection |
| Accelerometer / Gyroscope | No motion sensor support; use real devices for motion-dependent behavior |
| Barometer | No |
| NFC (Core NFC) | No |
| Bluetooth (Core Bluetooth) | No — use a real device for BLE testing |
| CarPlay display simulation | Supported through Simulator's external display/CarPlay option; still verify in vehicle or device setups |
| Face ID / Touch ID hardware | No hardware — use Features > Face ID / Touch ID menu in Simulator |
| Memory warnings, manual iCloud sync trigger, location changes | Supported through Simulator menus or simctl where available |
| Automatic iCloud sync behavior | Not fully simulated — validate notification-triggered sync, real account/device propagation, conflicts, and background behavior on hardware |
| Cellular network conditions | No — use Network Link Conditioner on Mac |
Treat Simulator performance as a functional smoke signal only. When answering performance-boundary questions, explicitly name CPU throughput/general processing speed and networking throughput/latency alongside graphics, Metal shader correctness, memory bandwidth, frame timing, memory pressure, Jetsam behavior, and thermal behavior.
Use this minimum wording in release-checklist answers: "Simulator is not an accurate performance proxy for processing/CPU, graphics/Metal, memory, networking throughput/latency, Metal GPU behavior, frame timing, memory pressure/Jetsam, or thermal behavior." If a prompt only names "Metal performance," still include processing, memory, and networking in the device-verification list.
UDIDs change when simulators are deleted and recreated. Hardcoded values break on other machines and CI.
# WRONG — hardcoded UDID
xcrun simctl boot "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
# CORRECT — look up by name and runtime
UDID=$(xcrun simctl list -j devices available | \
jq -r '.devices["com.apple.CoreSimulator.SimRuntime.iOS-18-4"][] | select(.name == "iPhone 16 Pro") | .udid')
xcrun simctl boot "$UDID"
# CORRECT — use "booted" when one simulator is running
xcrun simctl install booted MyApp.app
simctl install and simctl launch require a booted device. They fail silently or with an unhelpful error on a shutdown device.
# WRONG — device is not booted
xcrun simctl install <UDID> MyApp.app # fails
# CORRECT — boot first, then install
xcrun simctl boot <UDID>
xcrun simctl install <UDID> MyApp.app
xcrun simctl launch <UDID> com.example.MyApp
Each booted simulator consumes memory and CPU. CI pipelines that create simulators without cleanup accumulate zombie devices.
# WRONG — CI script creates and boots but never cleans up
xcrun simctl create "CI Phone" "iPhone 16 Pro" "com.apple.CoreSimulator.SimRuntime.iOS-18-4"
xcrun simctl boot "$UDID"
# ... tests run, pipeline exits ...
# CORRECT — always clean up in CI teardown
cleanup() {
xcrun simctl shutdown all
xcrun simctl delete "$UDID"
}
trap cleanup EXIT
simctl push bypasses the entire APNs infrastructure. It tests payload parsing and notification UI, not token registration, entitlements, or server-side delivery.
# WRONG — only testing with simctl, shipping without real device testing
xcrun simctl push booted com.example.MyApp payload.json
# "Push works!" — no, it only proves the app handles the payload
# CORRECT — use simctl for development iteration, then verify end-to-end on a real device
# 1. simctl push during development for fast iteration
# 2. Real device + APNs sandbox for integration testing before release
A simulator stuck in the "Booting" state will not recover by retrying boot. The underlying CoreSimulator state is corrupted.
# WRONG — retry loop on a stuck device
xcrun simctl boot "$UDID" # "Unable to boot device in current state: Booting"
xcrun simctl boot "$UDID" # same error, forever
# CORRECT — shut down, erase, and retry
xcrun simctl shutdown "$UDID"
xcrun simctl erase "$UDID"
xcrun simctl boot "$UDID"
# If that fails, reset CoreSimulator entirely
xcrun simctl shutdown all
xcrun simctl erase all
# Last resort: rm -rf ~/Library/Developer/CoreSimulator/Caches
booted or parsed UDID from JSON output, not hardcoded valuessimctl push during development#if targetEnvironment(simulator) guards around APIs unavailable in Simulatordevelopment
Implement, review, or improve data visualizations using Swift Charts. Use when building bar, line, area, point, pie, donut, or iOS 26 3D charts; when adding chart selection, scrolling, annotations, axes, scales, legends, or foregroundStyle grouping; when plotting functions with BarPlot, LinePlot, AreaPlot, PointPlot, Chart3D, or SurfacePlot; or when creating heat maps, Gantt charts, grouped bars, sparklines, threshold lines, or spatial visualizations.
data-ai
Select, implement, or migrate between app architecture patterns for Apple platform apps. Use when choosing between MV (Model-View with @Observable), MVVM, MVI, TCA (The Composable Architecture), Clean Architecture, VIPER, or Coordinator patterns; when evaluating architecture fit for a feature's complexity; when migrating from one pattern to another; or when reviewing whether an app's current architecture is appropriate. Scoped to Apple-platform patterns using Swift 6.3, SwiftUI, and UIKit.
development
Apply Swift API Design Guidelines to name, label, and document Swift APIs. Covers argument label rules (prepositional phrase rule, grammatical phrase rule, first-label omission), mutating/nonmutating pair naming (-ed/-ing participle pattern, form- prefix, sort/sorted, formUnion/union), side-effect naming (noun for pure, verb for mutating), documentation comment structure (summary by declaration kind, O(1) complexity rule), clarity at call site, role-based naming, protocol naming (-able/-ible/-ing), default arguments over method families, casing conventions, and terminology. Use when designing new Swift APIs, reviewing naming and argument labels, writing documentation comments, or refactoring for call site clarity.
development
Implement, review, or improve in-app purchases and subscriptions using StoreKit 2. Use when building paywalls with SubscriptionStoreView or ProductView, processing transactions with Product and Transaction APIs, verifying entitlements, handling purchase flows (consumable, non-consumable, auto-renewable), implementing offer codes or promotional/win-back/introductory offers, managing subscription status and renewal state, setting up StoreKit testing with configuration files, or integrating Family Sharing, Ask to Buy, refund handling, and billing retry logic.