I need to announce some text to all users of my application when they perform some action. To do so I use AVSpeechSynthesizer
. This works well, unless you use VoiceOver to perform the action. Because VoiceOver is announcing some system provided information to the user, then my AVSpeechUtterance
is played at the same time, so the voices overlap. How can I queue up my speech utterance so that it isn't played until after VoiceOver finishes speaking?

- 52,571
- 37
- 201
- 351
3 Answers
You can achieve this by observing VoiceOver activities. First add a Voiceover notification observer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(voiceOverDidStop:) name:UIAccessibilityAnnouncementDidFinishNotification object:nil];
Then, in the specified method:
-(void)voiceOverDidStop:(NSNotification*)n {
NSString* msg;
NSNumber* finished;
msg = [[n userInfo] objectForKey:UIAccessibilityAnnouncementKeyStringValue];
finished = [[n userInfo] objectForKey:UIAccessibilityAnnouncementKeyWasSuccessful];
if(finished) {
// send the AVSpeechSynthsizer message
}
}
Remember to remove the observe before disposing your app!
Another method you can use (if applicable) is to edit the accessibilityLabel
and accessibilityHint
properties of the object the user is acting with. Set those properties to @""
so VoiceOver knows there is nothing to say about that object.
Hope this helps you even if my answer came quite late : )

- 834
- 1
- 7
- 20
Check whether VoiceOver is running and post an UIAccessibilityAnnouncementNotification
with your message instead of using AVSpeechSynthesizer
.

- 20,509
- 6
- 47
- 58
-
That's not going to work in this case, as I need to control the utterance pitch and rate for this announcement. – Jordan H Mar 29 '15 at 02:59
-
In that case, I don't believe any existing API supports your requirements. – Justin Mar 29 '15 at 20:20
I've done this with asyncAfter
, the code is SwiftUI but I hope you get the idea:
Button {
if !UIAccessibility.isVoiceOverRunning {
readOutLoud(translation)
}
} label: {
Image(systemName: "speaker.wave.3.fill")
}
.accessibilityElement()
.accessibilityLabel("Listen")
.accessibilityAction {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
readOutLoud(translation)
})
}
The button reads a text when is pressed and VoiceOver is not active.
When VoiceOver is activated, the text will be read through the accessibilityAction
with the desired delay.

- 1,610
- 14
- 28