.agents/skills/core-motion/SKILL.md
Access accelerometer, gyroscope, magnetometer, pedometer, and activity-recognition data using CoreMotion. Use when reading device sensor data, counting steps, detecting user activity (walking/running/driving), tracking altitude changes, or implementing motion-based interactions in iOS/watchOS apps.
npx skillsauth add DFly7/iOS-FastAPI-Supabase-AI core-motionInstall 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.
Read device sensor data -- accelerometer, gyroscope, magnetometer, pedometer, and activity recognition -- on iOS and watchOS. CoreMotion fuses raw sensor inputs into processed device-motion data and provides pedometer/activity APIs for fitness and navigation use cases. Targets Swift 6.3 / iOS 26+.
Add NSMotionUsageDescription to Info.plist with a user-facing string explaining
why your app needs motion data. Without this key, the app crashes on first access.
<key>NSMotionUsageDescription</key>
<string>This app uses motion data to track your activity.</string>
CoreMotion uses CMAuthorizationStatus for pedometer and activity APIs. Sensor
APIs (accelerometer, gyro) do not require explicit authorization but do require
the usage description key.
import CoreMotion
let status = CMMotionActivityManager.authorizationStatus()
switch status {
case .notDetermined:
// Will prompt on first use
break
case .authorized:
break
case .restricted, .denied:
// Direct user to Settings
break
@unknown default:
break
}
Create exactly one CMMotionManager per app. Multiple instances degrade
sensor update rates.
import CoreMotion
let motionManager = CMMotionManager()
guard motionManager.isAccelerometerAvailable else { return }
motionManager.accelerometerUpdateInterval = 1.0 / 60.0 // 60 Hz
motionManager.startAccelerometerUpdates(to: .main) { data, error in
guard let acceleration = data?.acceleration else { return }
print("x: \(acceleration.x), y: \(acceleration.y), z: \(acceleration.z)")
}
// When done:
motionManager.stopAccelerometerUpdates()
guard motionManager.isGyroAvailable else { return }
motionManager.gyroUpdateInterval = 1.0 / 60.0
motionManager.startGyroUpdates(to: .main) { data, error in
guard let rotationRate = data?.rotationRate else { return }
print("x: \(rotationRate.x), y: \(rotationRate.y), z: \(rotationRate.z)")
}
motionManager.stopGyroUpdates()
For games, start updates without a handler and poll the latest sample each frame:
motionManager.startAccelerometerUpdates()
// In your game loop / display link:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// Move player based on tilt
}
Device motion fuses accelerometer, gyroscope, and magnetometer into a single
CMDeviceMotion object with attitude, user acceleration (gravity removed),
rotation rate, and calibrated magnetic field.
guard motionManager.isDeviceMotionAvailable else { return }
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(
using: .xArbitraryZVertical,
to: .main
) { motion, error in
guard let motion else { return }
let attitude = motion.attitude // roll, pitch, yaw
let userAccel = motion.userAcceleration
let gravity = motion.gravity
let heading = motion.heading // 0-360 degrees (requires magnetometer)
print("Pitch: \(attitude.pitch), Roll: \(attitude.roll)")
}
motionManager.stopDeviceMotionUpdates()
| Frame | Use Case |
|---|---|
| .xArbitraryZVertical | Default. Z is vertical, X arbitrary at start. Most games. |
| .xArbitraryCorrectedZVertical | Same as above, corrected for gyro drift over time. |
| .xMagneticNorthZVertical | X points to magnetic north. Requires magnetometer. |
| .xTrueNorthZVertical | X points to true north. Requires magnetometer + location. |
Check available frames before use:
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// Safe to use true north
}
CMPedometer provides step counts, distance, pace, cadence, and floor counts.
let pedometer = CMPedometer()
guard CMPedometer.isStepCountingAvailable() else { return }
// Historical query
pedometer.queryPedometerData(
from: Calendar.current.startOfDay(for: Date()),
to: Date()
) { data, error in
guard let data else { return }
print("Steps today: \(data.numberOfSteps)")
print("Distance: \(data.distance?.doubleValue ?? 0) meters")
print("Floors up: \(data.floorsAscended?.intValue ?? 0)")
}
// Live updates
pedometer.startUpdates(from: Date()) { data, error in
guard let data else { return }
print("Steps: \(data.numberOfSteps)")
}
// Stop when done
pedometer.stopUpdates()
| Method | What It Checks |
|---|---|
| isStepCountingAvailable() | Step counter hardware |
| isDistanceAvailable() | Distance estimation |
| isFloorCountingAvailable() | Barometric altimeter for floors |
| isPaceAvailable() | Pace data |
| isCadenceAvailable() | Cadence data |
Detects whether the user is stationary, walking, running, cycling, or in a vehicle.
let activityManager = CMMotionActivityManager()
guard CMMotionActivityManager.isActivityAvailable() else { return }
// Live activity updates
activityManager.startActivityUpdates(to: .main) { activity in
guard let activity else { return }
if activity.walking {
print("Walking (confidence: \(activity.confidence.rawValue))")
} else if activity.running {
print("Running")
} else if activity.automotive {
print("In vehicle")
} else if activity.cycling {
print("Cycling")
} else if activity.stationary {
print("Stationary")
}
}
activityManager.stopActivityUpdates()
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
activityManager.queryActivityStarting(
from: yesterday,
to: Date(),
to: .main
) { activities, error in
guard let activities else { return }
for activity in activities {
print("\(activity.startDate): walking=\(activity.walking)")
}
}
let altimeter = CMAltimeter()
guard CMAltimeter.isRelativeAltitudeAvailable() else { return }
altimeter.startRelativeAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("Relative altitude: \(data.relativeAltitude) meters")
print("Pressure: \(data.pressure) kPa")
}
altimeter.stopRelativeAltitudeUpdates()
For absolute altitude (GPS-based):
guard CMAltimeter.isAbsoluteAltitudeAvailable() else { return }
altimeter.startAbsoluteAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("Altitude: \(data.altitude)m, accuracy: \(data.accuracy)m")
}
altimeter.stopAbsoluteAltitudeUpdates()
| Interval | Hz | Use Case | Battery Impact |
|---|---|---|---|
| 1.0 / 10.0 | 10 | UI orientation | Low |
| 1.0 / 30.0 | 30 | Casual games | Moderate |
| 1.0 / 60.0 | 60 | Action games | High |
| 1.0 / 100.0 | 100 | Max rate (iPhone) | Very High |
Use the lowest frequency that meets your needs. CMMotionManager caps at 100 Hz
per sample. For higher frequencies, use CMBatchedSensorManager on watchOS/iOS 17+.
// WRONG -- degrades update rates for all instances
class ViewA { let motion = CMMotionManager() }
class ViewB { let motion = CMMotionManager() }
// CORRECT -- single instance, shared across the app
@Observable
final class MotionService {
static let shared = MotionService()
let manager = CMMotionManager()
}
// WRONG -- crashes on devices without gyroscope
motionManager.startGyroUpdates(to: .main) { data, _ in }
// CORRECT -- check first
guard motionManager.isGyroAvailable else {
showUnsupportedMessage()
return
}
motionManager.startGyroUpdates(to: .main) { data, _ in }
// WRONG -- updates keep running, draining battery
class MotionVC: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
motionManager.startAccelerometerUpdates(to: .main) { _, _ in }
}
// Missing viewDidDisappear stop!
}
// CORRECT -- stop in the counterpart lifecycle method
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
motionManager.stopAccelerometerUpdates()
}
// WRONG -- 100 Hz for a compass display
motionManager.deviceMotionUpdateInterval = 1.0 / 100.0
// CORRECT -- 10 Hz is more than enough for a compass
motionManager.deviceMotionUpdateInterval = 1.0 / 10.0
// WRONG -- checking only one property
if activity.walking { handleWalking() }
// CORRECT -- multiple can be true simultaneously; check confidence
if activity.walking && activity.confidence == .high {
handleWalking()
} else if activity.automotive && activity.confidence != .low {
handleDriving()
}
NSMotionUsageDescription present in Info.plist with a clear explanationCMMotionManager instance shared across the appisAccelerometerAvailable, etc.)start*Updates calls have matching stop*Updates in lifecycle counterpartsCMMotionActivity.confidence checked before acting on activity typedevelopment
Resolve Swift concurrency compiler errors, adopt approachable concurrency (SE-0466), and write data-race-safe async code. Use when fixing Sendable conformance errors, actor isolation warnings, or strict concurrency diagnostics; when adopting default MainActor isolation, @concurrent, nonisolated(nonsending), or Task.immediate; when designing actor-based architectures, structured concurrency with TaskGroup, or background work offloading; or when migrating from @preconcurrency to full Swift 6 strict concurrency.
development
Implement Swift Codable models for JSON and property-list encoding and decoding with JSONDecoder, JSONEncoder, CodingKeys, and custom init(from:) or encode(to:). Use when parsing API responses, remapping keys, flattening nested JSON, handling date or data decoding strategies, decoding heterogeneous arrays, or integrating Codable with URLSession, SwiftData, or UserDefaults.
development
Implement, review, or improve data visualizations using Swift Charts. Use when building bar, line, area, point, pie, or donut charts; when adding chart selection, scrolling, or annotations; when plotting functions with vectorized BarPlot, LinePlot, AreaPlot, or PointPlot; when customizing axes, scales, legends, or foregroundStyle grouping; or when creating specialized visualizations like heat maps, Gantt charts, stacked/grouped bars, sparklines, or threshold lines.
data-ai
Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.