2

I am adding accessibility to my iPhone game and make extensive use of UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @"string") to announce various things happening in the game. It works well in 99% of time, but I am having one problem.

In all cases, voiceover announcements are performed from a single method I added to the application delegate.

- (void)voiceoverAction:(NSString *)speakString delay:(NSTimeInterval) delay {
    if (![[[[UIDevice currentDevice] systemVersion] substringToIndex:1] isEqualToString:@"3"]) {
        if (UIAccessibilityIsVoiceOverRunning()) {
            UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, speakString);
            if (delay > 0) {
                [NSThread sleepForTimeInterval:delay];
            }
        }
    }
}

The delay is there so the announcement is spoken before the next event happens in the game. I could not find a better way to ensure the whole announcement was spoken before some animation or other event cut it off.

In every case but one the announcement is spoken immediately when this method is called. In one case there is an approximate 10 second pause before the speaking is performed. In this case even if I debug the code and set a breakpoint and execute the UIAccessibilityPostNotification line manually the line executes but nothing happens. Then 10 seconds later, without doing anything in the debugger, the iPhone just starts speaking the announcement.

The only thing that is special about this one announcement is that it is called from a touchesEnded: event of a UIScrollView. Other announcements are part of the overall game loop and are not based on touch events.

Any idea what might cause voiceover to queue up an accessibility notification and not immediately speak it?

Thanks in advance, Steve

mfaani
  • 33,269
  • 19
  • 164
  • 293
smmelzer
  • 230
  • 2
  • 10

3 Answers3

6

If you can support only iOS 6 and forwards then you can use UIAccessibilityAnnouncementDidFinishNotification to ensure that the announcement finished before continuing.

You would observe it like any other notification

// Observe announcementDidFinish to know when an announcment finishes
// and if it succuded or not. 
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(announcementFinished:)
                                             name:UIAccessibilityAnnouncementDidFinishNotification
                                           object:nil];

The notification you get back comes with the text of the announcement and if all the text was read or if the announcement was aborted. If you have multiple announcements then you can wait for the correct one.

// When an announcement finishes this will get called.
- (void)announcementFinished:(NSNotification *)notification {
    // Get the text and if it succeded (read the entire thing) or not
    NSString *announcment = notification.userInfo[UIAccessibilityAnnouncementKeyStringValue];
    BOOL wasSuccessful = [notification.userInfo[UIAccessibilityAnnouncementKeyWasSuccessful] boolValue];

    if (wasSuccessful) {
        // The entire announcement was read, you can continue.
    } else {
        // The announcement was aborted by something else being read ...
        // Decide what you want to do in this case. 
    } 
}
David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
  • Can you help me [this](http://stackoverflow.com/questions/12395588/gestures-not-getting-detected-in-accessibility-mode) question? I haven't yet found how to post notifications using `UIAccessibilityPostNotification`. I checked that `voiceOver` is indeed running by using this command: `UIAccessibilityIsVoiceOverRunning()` – Autonomous Jul 28 '14 at 01:58
  • This notification only fires for programmatically triggered announcements, not the standard UI announcements. I hacked a solution by triggering `UIAccessibilityAnnouncementDidFinishNotification` with a fake announcement `UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @"x");`. – Brenden Nov 04 '15 at 22:58
2

Instead of passing in a delay and doing a sleep, whenever you call this method to speak your announcement you can at the same time dispatch a block to execute after a delay using dispatch_after in order to trigger your next event. You could also pass in the block and delay to this method if you prefer and send the dispatch after from within this method.

jhabbott
  • 18,461
  • 9
  • 58
  • 95
1

Read my comment. This is a self-inflicted problem using NSThread sleepForTimeInterval. I have read multiple times this is bad form and it is, but I still don't see a better solution for accessibility voiceover announcements. I would like to see Apple either create a block (and therefore also use Objective-C methods) for this UIAccessibilityPostNotification call or a callback when the voiceover is done.

smmelzer
  • 230
  • 2
  • 10