diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js index f84ff29805b10d..65f7af70d17d26 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js @@ -18,9 +18,11 @@ var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var AccessibilityManager = NativeModules.AccessibilityManager; var VOICE_OVER_EVENT = 'voiceOverDidChange'; +var ANNOUNCEMENT_DID_FINISH_EVENT = 'announcementDidFinish'; type ChangeEventName = $Enum<{ change: string, + announcementFinished: string }>; var _subscriptions = new Map(); @@ -97,15 +99,30 @@ var AccessibilityInfo = { * - `change`: Fires when the state of the screen reader changes. The argument * to the event handler is a boolean. The boolean is `true` when a screen * reader is enabled and `false` otherwise. + * - `announcementFinished`: iOS-only event. Fires when the screen reader has + * finished making an announcement. The argument to the event handler is a dictionary + * with these keys: + * - `announcement`: The string announced by the screen reader. + * - `success`: A boolean indicating whether the announcement was successfully made. */ addEventListener: function ( eventName: ChangeEventName, handler: Function ): Object { - var listener = RCTDeviceEventEmitter.addListener( - VOICE_OVER_EVENT, - handler - ); + var listener; + + if (eventName === 'change') { + listener = RCTDeviceEventEmitter.addListener( + VOICE_OVER_EVENT, + handler + ); + } else if (eventName === 'announcementFinished') { + listener = RCTDeviceEventEmitter.addListener( + ANNOUNCEMENT_DID_FINISH_EVENT, + handler + ); + } + _subscriptions.set(handler, listener); return { remove: AccessibilityInfo.removeEventListener.bind(null, eventName, handler), @@ -121,6 +138,15 @@ var AccessibilityInfo = { AccessibilityManager.setAccessibilityFocus(reactTag); }, + /** + * iOS-Only. Post a string to be announced by the screen reader. + */ + announceForAccessibility: function( + announcement: string + ): void { + AccessibilityManager.announceForAccessibility(announcement); + }, + /** * Remove an event handler. */ diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index 5e415bf1298571..ee82c70c37fda0 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -71,6 +71,11 @@ - (instancetype)init selector:@selector(didReceiveNewVoiceOverStatus:) name:UIAccessibilityVoiceOverStatusChanged object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(accessibilityAnnouncementDidFinish:) + name:UIAccessibilityAnnouncementDidFinishNotification + object:nil]; self.contentSizeCategory = RCTSharedApplication().preferredContentSizeCategory; _isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning(); @@ -101,6 +106,20 @@ - (void)didReceiveNewVoiceOverStatus:(__unused NSNotification *)notification } } +- (void)accessibilityAnnouncementDidFinish:(__unused NSNotification *)notification +{ + NSDictionary *userInfo = notification.userInfo; + // Response dictionary to populate the event with. + NSDictionary *response = @{@"announcement": userInfo[UIAccessibilityAnnouncementKeyStringValue], + @"success": userInfo[UIAccessibilityAnnouncementKeyWasSuccessful]}; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_bridge.eventDispatcher sendDeviceEventWithName:@"announcementDidFinish" + body:response]; +#pragma clang diagnostic pop +} + - (void)setContentSizeCategory:(NSString *)contentSizeCategory { if (_contentSizeCategory != contentSizeCategory) { @@ -171,6 +190,11 @@ - (void)setMultipliers:(NSDictionary *)multipliers }); } +RCT_EXPORT_METHOD(announceForAccessibility:(NSString *)announcement) +{ + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement); +} + RCT_EXPORT_METHOD(getMultiplier:(RCTResponseSenderBlock)callback) { if (callback) {