How do you control how VoiceOver pronounces text: spell-out, language, punctuation and IPA?
Predict first
Do speech attributes attach to the right ranges?
import UIKit
@MainActor func run() {
let s = NSMutableAttributedString(string: "Order AB12 for Müller")
s.addAttribute(.accessibilitySpeechSpellOut, value: true, range: NSRange(location: 6, length: 4))
s.addAttribute(.accessibilitySpeechLanguage, value: "de-DE", range: NSRange(location: 15, length: 6))
var r = NSRange()
let a = s.attributes(at: 7, effectiveRange: &r)
print("spell-out run:", r.location, r.length, a[.accessibilitySpeechSpellOut] as Any)
let b = s.attributes(at: 16, effectiveRange: &r)
print("language run:", r.location, r.length, b[.accessibilitySpeechLanguage] as Any)
let name = NSAttributedString(string: "Nguyen", attributes: [.accessibilitySpeechIPANotation: "wɪn"])
print("ipa:", name.attribute(.accessibilitySpeechIPANotation, at: 0, effectiveRange: nil) as Any)
let v = UIView(); v.accessibilityLanguage = "fr-FR"
print("view language:", v.accessibilityLanguage ?? "nil")
print("plain text has no speech attributes:", NSAttributedString(string: "AB12").attributes(at: 0, effectiveRange: nil).isEmpty)
}
MainActor.assumeIsolated { run() }
- spell-out run: 6 4 Optional(1) / language run: 15 6 Optional(de-DE) / ipa: Optional(wɪn) / view language: fr-FR / plain text has no speech attributes: true
- spell-out run: 0 21 …
- language run: 0 6 Optional(en)
Mental model. A subtitle file can mark a line “whisper” or “in French”. Speech attributes are those stage directions for the screen reader’s voice.
VoiceOver speaks with the voice and language of the user’s settings, which is wrong for part of your text more often than you would expect: an order code “AB12” is read as a word, a German name inside an English sentence is read with English pronunciation, and an unusual surname is mangled. You can shape this per range with speech attributes on an attributed label or value. .accessibilitySpeechSpellOut reads letters and digits one at a time (“A B one two”), .accessibilitySpeechLanguage switches the voice for a run to a language such as de-DE, .accessibilitySpeechPunctuation speaks punctuation aloud (useful for code or an email address), .accessibilitySpeechIPANotation gives an exact pronunciation as IPA, and .accessibilitySpeechQueueAnnouncement queues an announcement instead of interrupting speech. The check builds the attributed string and confirms each attribute applies to exactly the range you gave it (the spell-out run starts at 6 and is 4 long, the language run covers “Müller”). For a whole element use accessibilityLanguage (a view returns the language you set; in SwiftUI .accessibilityLanguage is not needed, because a Text built from a localised string already carries its language).
Use the attributes where meaning depends on them, and use the attributed forms of label and value (accessibilityAttributedLabel, accessibilityAttributedValue, and the SwiftUI Text equivalents) so the information travels with the text. Do not over-engineer: normal words in the user’s language need nothing, and spell-out on a long string is tedious to listen to. Prefer to write labels the way they should be heard (“Account number ending 4 2 1 9” in a visible-text-free label) when you control the string, and keep currency, dates and numbers in locale-aware formatters (Q120) because VoiceOver reads formatted values better than ones assembled from parts. What the check verifies is that the attributes are stored and scoped; how a given voice sounds, and whether VoiceOver honours IPA for a particular voice, needs a device with VoiceOver and was not run.
Each attribute is stored on exactly the range it was added to; plain strings have none.
Common traps
- Leaving codes and identifiers to be read as words. “AB12” becomes gibberish; mark them as spell-out or write the spaced label.
- Setting the language on the whole screen when only one name differs. Scope the attribute to the run.
They might follow up with
- Is this the same as the voice that reads a web view? A web view takes
langattributes from its HTML. - Does it affect braille? Not run; speech attributes are about voice output.
Related questions
- 24.1.1 How does VoiceOver read your UI: accessibility elements, labels, values, hints and traits, and how do you fix…
- 24.1.2 How do custom actions, adjustable controls, rotors and announcements make complex UI usable with VoiceOver?
- 2.8.4 How do you format numbers, currency and percentages correctly?
- 2.6.3 How do you localise strings: Localizable.strings, string catalogs, String(localized:)?
← How do you make a custom modal screen work with VoiceOver: modal…