VoiceOver and the numbers that change during a set
Last verified August 22, 2026 · 7 min read
Put VoiceOver on a workout screen and the most important element turns out to be the worst behaved. The timer, the rep count, the pace, the live heart rate — the number the whole screen exists to show changes several times a second. A naive accessible implementation does one of two things with it. It says nothing, because nobody described the element and it is a text view inside a stack. Or it pushes every change out, and the user gets an uninterruptible stream of digits that never answers the only question they had, which was whether the set is over.
Apple documents a trait for exactly this shape of element, and it is not the one most engineers guess. This page is engineering guidance and makes no legal claims; the law and store-policy layer lives in compliance.
The trait that means "stop announcing me"#
Apple's page for updatesFrequently is short. The summary sentence: "The accessibility element frequently updates its label or value." The discussion: "Use this trait to characterize an accessibility element that updates its label or value too frequently to send update notifications. Include this trait when you want an assistive app to avoid handling continual notifications and, instead, poll for changes when it needs updated information."
Read the second half as a statement about who is in charge. Without the trait, the mental model is push: your value changes, and something downstream decides whether to speak it. With the trait, the model Apple describes is pull — the assistive app polls for changes when it needs updated information. Your job stops being when do I say this and becomes what does this say when somebody asks.
The fact sheet behind this cluster gives us the trait's identifier and those two sentences, so that is all we will print:
// UIAccessibilityTraits.updatesFrequently
let liveMetricTrait = UIAccessibilityTraits.updatesFrequently
Which elements on a training screen qualify is not subtle. Anything driven by a sensor stream or a display-link tick: elapsed time, rest countdown, pace, cadence, power, live heart rate, and the rep count coming out of your tracker. Where those numbers come from is a separate problem — Apple Watch live heart rate for the stream, how rep counting works for the counter, and the anatomy of a watchOS workout app for the session that owns them.
The value has to stand on its own#
Here the engineering effort moves, and this section is our judgement rather than Apple's text. If the element is polled, then a single read is the entire experience. There is no build-up, no previous value for context, no second chance. Four rules follow.
Say the unit. "142" is a number in a void; a value that names beats per minute is a heart rate. The same applies to kilograms, watts and minutes.
Round for speech, not for the pixel grid. A stopwatch that renders hundredths is fine to look at and unreadable aloud. Our judgement: expose whole seconds under a minute and minutes-plus-seconds above it, and let the visual layer keep its precision.
Keep the phrasing stable. If the same element returns "rest, 12 seconds remaining" and then "rest 11s left", the listener has to re-parse the sentence instead of hearing the one word that changed. Use one template per element and substitute the number.
Do not weld four metrics into one string. A combined "24:10, 142 bpm, 8 of 10" changes on three different clocks, and a polled read gives the user everything at once when they wanted one thing. Separate elements are separate stops.
| Element | How often it changes | What one poll should return | Should it interrupt |
|---|---|---|---|
| Elapsed workout time | every second | elapsed time, 24 minutes 10 seconds | No |
| Rest countdown | every second | rest, 12 seconds remaining | Only at zero |
| Rep count in the current set | roughly once per rep | 8 of 10 reps | No |
| Live heart rate | several times a second | heart rate 142 beats per minute | No |
| Set finished | once | — | Yes |
The events that earn an interruption#
A small number of moments genuinely need to reach the user without being asked for. Apple's page for the announcement notification describes it as "A notification that an app posts when it needs to convey an announcement to the assistive app," and adds: "Use this notification to provide accessibility information about events that don't update the app's UI, or that update the app's UI". Our extraction of that second sentence stops mid-clause. Apple's sentence continues; we did not capture the rest and will not complete it on Apple's behalf.
Which events qualify is our call, not Apple's. Ours is a short list, and keeping it short is the point: rest is over, the set is finished, the target you were holding has been hit, the session auto-paused, the sensor dropped out. Each of those is a state change the user must act on. A new pace reading is not.
Two consequences of a short list. First, an announcement lands in the same audio channel as your coaching prompts and whatever the user is already listening to, so budget them like you would budget a spoken cue — the same discipline that applies to live activities during a workout. Second, if you find yourself wanting six announcements a minute, the problem is that a frequently-updating value has been mistaken for an event.
Things that vanish before anyone has heard them#
Apple documents isVoiceOverRunning as "A Boolean value that indicates whether VoiceOver is in an enabled state," and gives the reason to check it: "You can use this function to customize your app's UI specifically for VoiceOver users. For example, you might want UI elements that usually disappear quickly to persist onscreen for VoiceOver users."
That example is a description of the average fitness app. The three-two-one countdown before an interval. The toast confirming a set was logged. The badge that flashes when a personal record falls. The undo affordance that lives for two seconds. Every one of them is a design that assumes a glance.
// UIAccessibility.isVoiceOverRunning
if UIAccessibility.isVoiceOverRunning { /* let the toast persist until dismissed */ }
Two cautions, both ours. The flag tells you the screen reader is enabled and nothing else; it is not a measure of how quickly anyone reads, and it says nothing about the other reasons somebody might want a slower interface. And a check like this should extend a lifetime or add a dismissal control — it should not become the branch where a second, thinner version of your workout screen grows.
What this page is claiming#
Everything above about platform behaviour is what Apple's accessibility documentation says. None of it is behaviour we measured on a device, and there are no timings, no latency figures and no battery numbers here, because we have not run that test. Treat the trait, the notification and the flag as documented contracts and verify the result on hardware with real users of the feature — the mechanics of getting devices into that loop are in device lab and CI.
One more gap worth naming: the fact sheet for this cluster contains no verified Android counterpart to updatesFrequently. We are not going to claim one exists, or that none does. Android's side of the workout screen is a separate question with its own documented answers.
Monday morning#
Open the workout screen and list every element whose text changes more than once a second. That list is your updatesFrequently set; apply the trait and stop trying to announce them. Then rewrite each of those value strings so one read makes sense with nothing before it — unit included, rounded for speech, one template per element. Write down the events that deserve to interrupt, cap the list at five, and delete anything on it that is really just a number moving. Finally, grep for every view that dismisses itself on a timer and decide, for each one, what it does when VoiceOver is running.
Frequently asked questions
- Should a rep counter announce every rep to a VoiceOver user?
- No. A rep count is the kind of element Apple's updatesFrequently documentation describes: one that updates its label or value too frequently to send update notifications. Apple says to include the trait when you want an assistive app to avoid handling continual notifications and, instead, poll for changes when it needs updated information. So the counter carries the trait and holds a current, self-contained value such as eight of ten reps. Reserve an interruption for the moment the set completes, which is an event rather than a moving number. That keeps the channel free for the thing the user actually has to act on.
- What is the difference between the updatesFrequently trait and posting an announcement?
- They solve opposite problems. Apple documents updatesFrequently for an element that updates its label or value too frequently to send update notifications, and says to include it when you want an assistive app to poll for changes instead. Apple documents the announcement notification as one an app posts when it needs to convey an announcement to the assistive app, for events that do not update the app's UI, or that update it in a way the extraction we hold does not finish describing. A live pace readout is the first case. Rest being over is the second.
- How do I stop a two-second rest toast from disappearing before a screen reader reaches it?
- Check whether the screen reader is on and change the lifetime. Apple documents isVoiceOverRunning as a Boolean value indicating whether VoiceOver is in an enabled state, and gives this exact example: you might want UI elements that usually disappear quickly to persist onscreen for VoiceOver users. Practically, keep the toast until it is dismissed, or give it an explicit dismissal control rather than a timer. Our judgement is to change the lifetime and nothing else. The flag is not a licence to branch into a separate, reduced version of the workout screen.
Keep reading
Elsewhere on the site
Pages that share this one’s concepts and sources, from other sections.
Next steps
Was this page useful?
Independent comparison, last reviewed August 22, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All accessibility · by AIFitnessAPI