Fixing watchOS Workout Exit Bugs with Explicit State Transitions
Workout timers failing to stop are usually not timer bugs. They are state-model bugs. If your app does not model pause/resume/end transitions explicitly, exit flows become unpredictable.
Step 1: Define canonical workout states
enum WorkoutState {
case idle
case running(startedAt: Date)
case paused(elapsed: TimeInterval)
case ended(total: TimeInterval)
}
Step 2: Route UI actions through one reducer
func reduce(_ state: WorkoutState, _ action: Action) -> WorkoutState {
switch (state, action) {
case (.running(let startedAt), .pause):
return .paused(elapsed: Date().timeIntervalSince(startedAt))
default:
return state
}
}
Step 3: Sync terminal state to iPhone immediately
connectivity.send(["state": "ended", "total": totalSeconds])
Pitfalls
- Multiple timers owned by different views.
- Exit buttons mutating state and navigation together.
- No watch-to-phone sync on app background.
Validation
- End action always produces exactly one terminal state.
- Reopen app after background still shows consistent totals.
- Phone summary matches watch end payload.