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.

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.