CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

71

Quality

89%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

motion-patterns.mdskills/core-motion/references/

CoreMotion Extended Patterns

Overflow reference for the core-motion skill. Contains advanced patterns that exceed the main skill file's scope.

Contents

  • SwiftUI Integration with @Observable
  • CMBatchedSensorManager (High-Frequency)
  • Headphone Motion
  • Pedometer SwiftUI View
  • Activity-Based Navigation
  • Water Submersion (watchOS Ultra)

SwiftUI Integration with @Observable

Motion Manager Service

import CoreMotion
import SwiftUI

@Observable
@MainActor
final class MotionService {
    static let shared = MotionService()

    private let manager = CMMotionManager()

    var pitch: Double = 0
    var roll: Double = 0
    var yaw: Double = 0
    var userAcceleration: CMAcceleration = CMAcceleration()
    var isActive = false

    func startDeviceMotion(interval: TimeInterval = 1.0 / 60.0) {
        guard manager.isDeviceMotionAvailable, !isActive else { return }

        manager.deviceMotionUpdateInterval = interval
        manager.startDeviceMotionUpdates(
            using: .xArbitraryZVertical,
            to: .main
        ) { [weak self] motion, error in
            guard let self, let motion else { return }
            self.pitch = motion.attitude.pitch
            self.roll = motion.attitude.roll
            self.yaw = motion.attitude.yaw
            self.userAcceleration = motion.userAcceleration
        }
        isActive = true
    }

    func stop() {
        manager.stopDeviceMotionUpdates()
        isActive = false
    }
}

SwiftUI View Using Motion

struct TiltView: View {
    @State private var motionService = MotionService.shared

    var body: some View {
        VStack {
            Circle()
                .fill(.blue)
                .frame(width: 60, height: 60)
                .offset(
                    x: motionService.roll * 100,
                    y: motionService.pitch * 100
                )

            Text("Roll: \(motionService.roll, format: .number.precision(.fractionLength(2)))")
            Text("Pitch: \(motionService.pitch, format: .number.precision(.fractionLength(2)))")
        }
        .onAppear { motionService.startDeviceMotion() }
        .onDisappear { motionService.stop() }
    }
}

Level Indicator

struct LevelIndicator: View {
    @State private var motionService = MotionService.shared

    private var isLevel: Bool {
        abs(motionService.pitch) < 0.05 && abs(motionService.roll) < 0.05
    }

    var body: some View {
        ZStack {
            Circle()
                .stroke(isLevel ? .green : .gray, lineWidth: 3)
                .frame(width: 200, height: 200)

            Circle()
                .fill(isLevel ? .green : .red)
                .frame(width: 20, height: 20)
                .offset(
                    x: motionService.roll * 100,
                    y: motionService.pitch * -100
                )
        }
        .onAppear { motionService.startDeviceMotion(interval: 1.0 / 30.0) }
        .onDisappear { motionService.stop() }
    }
}

CMBatchedSensorManager (High-Frequency)

CMBatchedSensorManager delivers batched data at higher frequencies (up to 800 Hz on Apple Watch Ultra). Available on iOS 17+ and watchOS 10+.

AsyncSequence Pattern

import CoreMotion

@Observable
@MainActor
final class BatchedMotionService {
    private let batchedManager = CMBatchedSensorManager()
    private var updateTask: Task<Void, Never>?

    var latestAcceleration: CMAcceleration?

    func startBatchedAccelerometer() {
        guard CMBatchedSensorManager.isAccelerometerSupported else { return }

        updateTask = Task {
            for await batch in batchedManager.accelerometerUpdates() {
                guard !Task.isCancelled else { break }
                // Process entire batch
                for sample in batch {
                    // sample.acceleration, sample.timestamp
                }
                // Update UI with most recent
                if let latest = batch.last {
                    latestAcceleration = latest.acceleration
                }
            }
        }
    }

    func stop() {
        updateTask?.cancel()
        updateTask = nil
    }
}

Configuring Frequency

let batchedManager = CMBatchedSensorManager()

// Set frequency before starting updates
batchedManager.accelerometerDataFrequency = 200  // Hz
batchedManager.startAccelerometerUpdates()

Headphone Motion

Track head motion using AirPods Pro / AirPods Max via CMHeadphoneMotionManager.

import CoreMotion

@Observable
@MainActor
final class HeadphoneMotionService: NSObject {
    private let headphoneManager = CMHeadphoneMotionManager()

    var isConnected = false
    var headPitch: Double = 0
    var headYaw: Double = 0

    func start() {
        guard headphoneManager.isDeviceMotionAvailable else { return }

        headphoneManager.delegate = self
        headphoneManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in
            guard let self, let motion else { return }
            self.headPitch = motion.attitude.pitch
            self.headYaw = motion.attitude.yaw
        }
    }

    func stop() {
        headphoneManager.stopDeviceMotionUpdates()
    }
}

extension HeadphoneMotionService: CMHeadphoneMotionManagerDelegate {
    nonisolated func headphoneMotionManagerDidConnect(
        _ manager: CMHeadphoneMotionManager
    ) {
        Task { @MainActor in isConnected = true }
    }

    nonisolated func headphoneMotionManagerDidDisconnect(
        _ manager: CMHeadphoneMotionManager
    ) {
        Task { @MainActor in isConnected = false }
    }
}

Pedometer SwiftUI View

Step Counter Dashboard

import CoreMotion
import SwiftUI

@Observable
@MainActor
final class PedometerService {
    private let pedometer = CMPedometer()

    var todaySteps: Int = 0
    var todayDistance: Double = 0
    var floorsAscended: Int = 0

    func fetchToday() {
        guard CMPedometer.isStepCountingAvailable() else { return }

        let startOfDay = Calendar.current.startOfDay(for: Date())

        pedometer.queryPedometerData(from: startOfDay, to: Date()) { [weak self] data, error in
            guard let self, let data else { return }
            Task { @MainActor in
                self.todaySteps = data.numberOfSteps.intValue
                self.todayDistance = data.distance?.doubleValue ?? 0
                self.floorsAscended = data.floorsAscended?.intValue ?? 0
            }
        }
    }

    func startLiveUpdates() {
        guard CMPedometer.isStepCountingAvailable() else { return }

        let startOfDay = Calendar.current.startOfDay(for: Date())

        pedometer.startUpdates(from: startOfDay) { [weak self] data, error in
            guard let self, let data else { return }
            Task { @MainActor in
                self.todaySteps = data.numberOfSteps.intValue
                self.todayDistance = data.distance?.doubleValue ?? 0
                self.floorsAscended = data.floorsAscended?.intValue ?? 0
            }
        }
    }

    func stopLiveUpdates() {
        pedometer.stopUpdates()
    }
}

SwiftUI Dashboard View

struct StepDashboard: View {
    @State private var pedometerService = PedometerService()

    var body: some View {
        List {
            Section("Today") {
                LabeledContent("Steps") {
                    Text("\(pedometerService.todaySteps)")
                }
                LabeledContent("Distance") {
                    Text(
                        Measurement(value: pedometerService.todayDistance, unit: UnitLength.meters),
                        format: .measurement(width: .abbreviated)
                    )
                }
                if CMPedometer.isFloorCountingAvailable() {
                    LabeledContent("Floors Climbed") {
                        Text("\(pedometerService.floorsAscended)")
                    }
                }
            }
        }
        .onAppear { pedometerService.startLiveUpdates() }
        .onDisappear { pedometerService.stopLiveUpdates() }
    }
}

Activity-Based Navigation

Switch between driving and walking modes automatically:

import CoreMotion

@Observable
@MainActor
final class NavigationModeService {
    private let activityManager = CMMotionActivityManager()

    enum Mode: String {
        case walking, driving, cycling, unknown
    }

    var currentMode: Mode = .unknown

    func startMonitoring() {
        guard CMMotionActivityManager.isActivityAvailable() else { return }

        activityManager.startActivityUpdates(to: .main) { [weak self] activity in
            guard let self, let activity,
                  activity.confidence != .low else { return }

            Task { @MainActor in
                if activity.automotive {
                    self.currentMode = .driving
                } else if activity.cycling {
                    self.currentMode = .cycling
                } else if activity.walking || activity.running {
                    self.currentMode = .walking
                } else if activity.stationary {
                    // Keep previous mode when stationary (e.g., at a stoplight)
                }
            }
        }
    }

    func stopMonitoring() {
        activityManager.stopActivityUpdates()
    }
}

Water Submersion (watchOS Ultra)

Track water depth and temperature for dive apps on Apple Watch Ultra.

import CoreMotion

@Observable
@MainActor
final class DiveService: NSObject {
    private let submersionManager = CMWaterSubmersionManager()

    var isSubmerged = false
    var currentDepth: Double?
    var waterTemperature: Double?

    func start() {
        guard CMWaterSubmersionManager.waterSubmersionAvailable else { return }
        submersionManager.delegate = self
    }
}

extension DiveService: CMWaterSubmersionManagerDelegate {
    nonisolated func manager(
        _ manager: CMWaterSubmersionManager,
        didUpdate event: CMWaterSubmersionEvent
    ) {
        Task { @MainActor in
            isSubmerged = event.state == .submerged
        }
    }

    nonisolated func manager(
        _ manager: CMWaterSubmersionManager,
        didUpdate measurement: CMWaterSubmersionMeasurement
    ) {
        Task { @MainActor in
            currentDepth = measurement.depth?.value
        }
    }

    nonisolated func manager(
        _ manager: CMWaterSubmersionManager,
        didUpdate temperature: CMWaterTemperature
    ) {
        Task { @MainActor in
            waterTemperature = temperature.temperature.value
        }
    }

    nonisolated func manager(
        _ manager: CMWaterSubmersionManager,
        errorOccurred error: any Error
    ) {
        print("Submersion error: \(error)")
    }
}

Important: CMWaterSubmersionManager requires the com.apple.developer.submerged-shallow-depth-and-pressure entitlement and runs only on Apple Watch Ultra hardware.

skills

core-motion

CHANGELOG.md

README.md

tile.json