ezefranca/watchshaker
Native shake detection for Apple Watch, designed for Swift 6 and SwiftUI.
Why WatchShaker
- Swift 6 strict-concurrency safety with main-actor observable state
- Native SwiftUI lifecycle through
.onWatchShake - Independent
AsyncStreamsubscriptions for structured concurrency - Peak capture, hysteresis, cooldown, and monotonic sensor timing
- Device-motion user acceleration with a filtered accelerometer fallback
- Extended runtime integration without pretending watchOS allows unrestricted background work
- Codable, Sendable event value types and deterministic Swift Testing coverage
Requirements
- watchOS 10 or later
- Swift 6 language mode
- Xcode 16 or later
The simulator doesn't provide representative motion data. Validate gesture thresholds and background behavior on Apple Watch hardware.
Keep one WatchShaker instance active at a time. The package shares one CMMotionManager, following Core Motion's guidance and avoiding competing sampling rates.
Installation
Add WatchShaker with Xcode's File → Add Package Dependencies, using:
https://github.com/ezefranca/WatchShaker.gitOr add it to a package manifest:
dependencies: [
.package(
url: "https://github.com/ezefranca/WatchShaker.git",
from: "2.0.1"
)
]SwiftUI
For most apps, attach the lifecycle-aware modifier to the view that owns the feature:
import SwiftUI
import WatchShaker
struct ContentView: View {
@State private var shakeCount = 0
var body: some View {
Text("Shakes: \(shakeCount)")
.onWatchShake { shake in
shakeCount += 1
print(shake.direction, shake.acceleration)
}
}
}Monitoring starts with the view's task and stops when that task is cancelled.
Observable state and async events
Use an owned @State instance when the UI needs lifecycle or latest-event state. WatchShaker uses Observation, so @StateObject is no longer appropriate.
struct MotionView: View {
@State private var shaker = WatchShaker(
configuration: .init(sensitivity: .high)
)
var body: some View {
Text(shaker.latestShake?.direction.rawValue ?? "Ready")
.task {
let events = shaker.shakes()
do {
try shaker.start(in: .foreground)
for await shake in events {
print("Peak: \(shake.acceleration.magnitude) g")
}
} catch {
print(error.localizedDescription)
}
shaker.stop()
}
}
}isAvailable describes hardware capability. It does not indicate that a shake occurred. Read latestShake, use onShake, or consume shakes() for events.
Configuration
let configuration = WatchShakerConfiguration(
sensitivity: .normal,
sampling: .balanced,
detection: .balanced
)Higher sensitivity (.high or .veryHigh) recognizes gentler gestures. Sampling offers .lowPower, .balanced, and .responsive; detection offers .responsive, .balanced, and .conservative profiles. Begin with the balanced defaults and tune on real hardware with the movement your app actually teaches.
Raw detector constants are kept out of the normal initializer. Applications with measured, hardware-specific requirements can create validated custom sampling and detection values. See Configuration.
Background execution
Yes—WatchShaker can keep monitoring during an eligible watchOS extended runtime session:
.onWatchShake(mode: .extendedRuntime) { shake in
handle(shake)
}Your watch app target must enable Background Modes and select the session type that truthfully matches the feature. watchOS, not the package, grants runtime and enforces time and energy limits. Physical therapy and smart alarm sessions can run in the background; self care and mindfulness sessions remain frontmost. A workout app should own an HKWorkoutSession and use WatchShaker's .foreground mode while that workout grants execution time.
See Background Monitoring before shipping this mode.
Migrating from 1.x
The current API removes ambiguous fake state and adopts modern naming. Deprecated aliases cover many 1.x spellings, but lifecycle and event handling should be migrated deliberately.
See the Migration Guide for a complete mapping.
Documentation
Read the native DocC documentation or browse the versioned copy hosted by the Swift Package Index. The public site also publishes agent-readable documentation and structured package context.
Build the complete DocC site with:
xcodebuild docbuild \
-scheme WatchShaker \
-destination 'generic/platform=watchOS Simulator'Research
WatchShaker was presented at ICECCME 2024. Read the paper on IEEE Xplore or view the preprint.
Contributing and license
Contributions are welcome. Read CONTRIBUTING.md and the security policy before opening a change.
WatchShaker is available under the MIT License. See LICENSE.
Package Metadata
Repository: ezefranca/watchshaker
Default branch: main
README: README.md