Announcement Spam

Status: ready

Symptom

The app announces every small change: each keystroke in search, each badge update, each timer tick, each loading step. VoiceOver speech is interrupted before the user finishes hearing anything.

Where it shows up

Search, cart badges, maps, feeds, delivery tracking, media controls, live validation, order status screens.

Why it blocks the task

An announcement interrupts whatever VoiceOver is saying. Reading a product card gets cut off by "Updated". Typing a card number gets cut off by "Invalid" after every digit. A 1-second timer competes with everything else. When everything is announced, the one announcement that matters is lost in the stream.

This is usually the overcorrection of Silent Error: the team learned that silent changes are bad and started announcing all of them.

Root cause

No rule separates a task result from a cosmetic update.

  1. Every state change posts an announcement.
  2. Stable state (count, progress) isn't in accessibilityValue, so announcing is the only way to expose it.
  3. Frequent updates aren't debounced.
  4. Validation runs on every keystroke and announces.
  5. Nobody decided what is allowed to interrupt the user.

Before

func searchTextDidChange(_ text: String) {
    reloadResults()
    UIAccessibility.post(notification: .announcement, argument: "Results updated")
}

What the user hears while typing "jasmine":

j
Results updated
ja
Results updated
jas
Results updated

Fix

Verdict: announce only a task result, once. Keep changing state in accessibilityValue where the user can read it on demand.

Why: the value is always there when the user asks. The announcement interrupts. Only a result the user is waiting for justifies an interruption.

Change What to do
Search results while typing Announce the count once, after typing stops (about 600 ms)
Added to cart One announcement: "Added to cart. 3 items."
Cart badge count accessibilityValue on the cart button
Upload progress accessibilityValue + .updatesFrequently. Announce completion only
Countdown timer accessibilityValue. Announce at 2 minutes and at 30 seconds, not every second
Field validation On submit, not per keystroke

SwiftUI, search count after typing stops:

.task(id: query) {
    do { try await Task.sleep(for: .milliseconds(600)) } catch { return }
    AccessibilityNotification.Announcement(
        results.isEmpty ? "No results" : "\(results.count) results"
    ).post()
}

Each new keystroke cancels the previous task, so only the last one announces.

UIKit, progress:

progressView.accessibilityLabel = "Uploading receipt"
progressView.accessibilityValue = "60 percent"
progressView.accessibilityTraits.insert(.updatesFrequently)

// on completion only
UIAccessibility.post(notification: .announcement, argument: "Receipt uploaded")

What we don't do

  • We don't announce on every keystroke.
  • We don't announce a value that's already in accessibilityValue on the focused element. VoiceOver reads it anyway.
  • We don't use an announcement for an error that needs a fix. That needs focus (Focus Recovery).

Check

Profile:   VoiceOver
Task:      type "jasmine" in search
Expected:  nothing announced while typing
           one announcement with the count after typing stops
Profile:   VoiceOver
Task:      add an item to the cart, then move to the cart button
Expected:  one confirmation
           the cart button reads the count as its value
Profile:   VoiceOver
Task:      stay on the order status screen for 60 seconds
Expected:  no speech unless the order stage changes
           the timer value is read when focused

WCAG mapping

  1. 4.1.3 Status Messages: status changes are available without moving focus.
  2. 2.2.2 Pause, Stop, Hide: auto-updating content doesn't take over.
  3. 3.2.2 On Input: typing doesn't cause unexpected changes.

App Store and product risk

Not a rejection. It makes the flow tiring enough that VoiceOver users give up. A team that claims VoiceOver support needs task completion evidence, not "we added announcements".

  1. Meaningful Announcement
  2. State as Value
  3. Loading State Contract
  4. Time Extension
  5. Testing Evidence Record
  1. Chapter 5
  2. Chapter 11
  3. Chapter 13
  4. Chapter 17

Sources

  1. Apple Developer: AccessibilityNotification
  2. Apple Developer: UIAccessibility.Notification
  3. Apple Developer: UIAccessibilityTraits updatesFrequently
  4. W3C: Understanding SC 4.1.3 Status Messages