Watch-first, not watch-also: designing intervals for the wrist
Most running apps treat the Apple Watch as a remote control for the phone in your pocket. iRunning is built the other way around: the watch is where the run happens. Make that one decision and almost every UI choice flips.
"Watch-first" gets said a lot and meant rarely. The usual shape is a phone app that grew a watch companion — the watch shows a couple of metrics and a stop button, and the real product lives on the big screen. That's watch-also. iRunning has no subscription wall and no expectation that your phone is even with you: you build a workout, leave the phone at home, and run it from your wrist. Designing for that is a different job.
The watch is a glance, not a screen
A phone screen gets your full attention for seconds at a time. A watch screen gets maybe one second, at arm's length, while you're moving — cold fingers, bouncing arm, sweat on the glass, sun washing it out. You're not reading; you're checking. That single constraint kills most "dashboard" layouts before you draw them.
So the rule I landed on: one number owns the screen. During an interval, the thing you care about is how long this effort lasts — so that's enormous, high-contrast, centered. Everything else (pace, heart rate, total time) is secondary and sized like it. If a runner has to hunt for the number that matters, the layout has already failed.
Design the current interval, not the workout
The temptation is to show the plan: a little timeline of all your intervals with a marker creeping along it. It looks great in a screenshot and it's useless at a glance. Mid-run you don't care about interval 7 of 10; you care about this one, and maybe a hint of what's next.
iRunning shows the current step as the whole screen — what to do, and the time left in it — with the next step as a single quiet line underneath. The full plan lives on the phone, before and after. On the wrist, the app is always answering one question: what am I doing right now, and for how long?
Talk through haptics, not the screen
Here's the part that took me longest to internalize: during a hard interval the runner is not looking at the watch. They're looking at the road. So the screen can't be how you tell them an interval changed — the wrist has to.
Each transition gets a distinct haptic. Going into work is a firm, assertive tap; dropping into recovery is a softer one; finishing the session is the success pattern. After a few runs your wrist learns them and you stop looking at the watch almost entirely — which is the goal.
import WatchKit
enum IntervalPhase { case work, recovery, finished }
func signal(_ phase: IntervalPhase) {
let device = WKInterfaceDevice.current()
switch phase {
case .work: device.play(.start) // firm: go
case .recovery: device.play(.stop) // softer: ease off
case .finished: device.play(.success)
}
}
The screen still updates, of course — but as confirmation, for the glance that comes a second later, not as the primary signal. Haptics drive; the display follows.
The simulator lies, and so does holding the watch in your hand at your desk. Glanceability and haptic timing only tell the truth when you're actually running — arm swinging, out of breath, mid-stride. Half of my layout decisions reversed the first time I tested them on a real run.
Let WorkoutKit carry the structure
You can hand-roll interval timing with a Timer, but on watchOS that's a fight you'll lose against the system — background execution, the workout-session lifecycle, the Fitness integration. WorkoutKit lets you describe the workout as data and hand it to the system, which then owns the transitions:
import WorkoutKit
// 5 × (2 min work / 90 s recovery), with a warm-up and cool-down.
let work = IntervalStep(.work, goal: .time(2, .minutes))
let recovery = IntervalStep(.recovery, goal: .time(90, .seconds))
let block = IntervalBlock(steps: [work, recovery], iterations: 5)
let session = CustomWorkout(
activity: .running,
location: .outdoor,
displayName: "5 × 2 min",
warmup: WorkoutStep(goal: .time(10, .minutes)),
blocks: [block],
cooldown: WorkoutStep(goal: .time(5, .minutes))
)
let plan = WorkoutPlan(.custom(session)) // hand the structure to the system
Describing the workout instead of driving it by hand means the system fires the transitions, keeps the session alive, and writes it to Health the same way a built-in workout would. My job shrinks to the part that's actually mine: how each transition feels on the wrist.
One tap, big targets, no gestures
Input on a moving wrist is hostile. Sweat breaks capacitive touch, gloves break it entirely, and a swipe-to-confirm you nailed at your desk is a coin flip at mile six. So the controls collapse to the essentials: a single large pause/resume target, and the Digital Crown — which works wet and gloved — for anything that needs a value. No custom swipe gestures, no tiny buttons, nothing that punishes a shaky tap.
What watch-first costs
Going watch-first doesn't mean abandoning the phone; it means giving it the opposite job. The phone is the thinking screen — building workouts, browsing history, reading the route and the splits after the fact, where a big screen and your full attention are assets. The watch is the doing screen. WatchConnectivity keeps the two in sync, with the watch as the source of truth for anything recorded on a run.
The honest tradeoff: it's more work. You're designing two apps with different rules instead of one app and a readout. But the result is something you can actually use the way a running watch is meant to be used — phone on the kitchen table, nothing in your hands, just a number and a buzz on your wrist telling you to go.