Skip to content

Commit

Permalink
Adding support for custom accessibility actions on iOS.
Browse files Browse the repository at this point in the history
Summary:
This feature has been requested by customers.  Our previous (pre-react) application had support for custom accessibility actions.

This feature allows UI elements to provide a list of custom actions that can be read when VoiceOver is enabled.  UI elements expose one accessibility action by default.  Some UI elements may support multiple actions though other mechanisms like tap and hold.  To expose these actions in an accessible way iOS provides custom accessibility actions.

Feature was tested in the iOS simulator using the Accessibility Inspector.  Custom actions were added to a button and observed in the tool.  Custom actions were also invoked using the tool and then stepped through in the debugger.

The feature was also tested on an iPhone.  VoiceOver was enabled on the device and custom actions were observed for controls that exposed them.

We have been using this feature in our app for some time as well.

[IOS] [ENHANCEMENT] [Accessibility] - Added support for custom accessibility actions

Eric Davison
Microsoft Corp.
Closes #17020

Differential Revision: D6472283

Pulled By: shergin

fbshipit-source-id: 4ac4697dca07028e87ffe71b70c00280e7f2043c
  • Loading branch information
ericdavmsft authored and facebook-github-bot committed Dec 5, 2017
1 parent ff3dc2e commit 36ad813
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Libraries/Components/View/ReactNativeViewAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var ReactNativeViewAttributes = {};
ReactNativeViewAttributes.UIView = {
pointerEvents: true,
accessible: true,
accessibilityActions: true,
accessibilityLabel: true,
accessibilityComponentType: true,
accessibilityLiveRegion: true,
Expand All @@ -28,6 +29,7 @@ ReactNativeViewAttributes.UIView = {
renderToHardwareTextureAndroid: true,
shouldRasterizeIOS: true,
onLayout: true,
onAccessibilityAction: true,
onAccessibilityTap: true,
onMagicTap: true,
collapsable: true,
Expand Down
17 changes: 17 additions & 0 deletions Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ export type ViewLayoutEvent = {
export type ViewProps = {
accessible?: bool,
accessibilityLabel?: React$PropType$Primitive<any>,
accessibilityActions?: Array<string>,
accessibilityComponentType?: AccessibilityComponentType,
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive',
importantForAccessibility?: 'auto'| 'yes'| 'no'| 'no-hide-descendants',
accessibilityTraits?: AccessibilityTrait | Array<AccessibilityTrait>,
accessibilityViewIsModal?: bool,
onAccessibilityAction?: Function,
onAccessibilityTap?: Function,
onMagicTap?: Function,
testID?: string,
Expand Down Expand Up @@ -99,6 +101,13 @@ module.exports = {
*/
accessibilityLabel: PropTypes.node,

/**
* Provides an array of custom actions available for accessibility.
*
* @platform ios
*/
accessibilityActions: PropTypes.arrayOf(PropTypes.string),

/**
* Indicates to accessibility services to treat UI component like a
* native one. Works for Android only.
Expand Down Expand Up @@ -165,6 +174,14 @@ module.exports = {
*/
accessibilityViewIsModal: PropTypes.bool,

/**
* When `accessible` is true, the system will try to invoke this function
* when the user performs an accessibility custom action.
*
* @platform ios
*/
onAccessibilityAction: PropTypes.func,

/**
* When `accessible` is true, the system will try to invoke this function
* when the user performs accessibility tap gesture.
Expand Down
6 changes: 6 additions & 0 deletions React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@
/**
* Accessibility event handlers
*/
@property (nonatomic, copy) RCTDirectEventBlock onAccessibilityAction;
@property (nonatomic, copy) RCTDirectEventBlock onAccessibilityTap;
@property (nonatomic, copy) RCTDirectEventBlock onMagicTap;

/**
* Accessibility properties
*/
@property (nonatomic, copy) NSArray <NSString *> *accessibilityActions;

/**
* Used to control how touch events are processed.
*/
Expand Down
30 changes: 30 additions & 0 deletions React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,36 @@ - (NSString *)accessibilityLabel
return RCTRecursiveAccessibilityLabel(self);
}

- (NSArray <UIAccessibilityCustomAction *> *)accessibilityCustomActions
{
if (!_accessibilityActions.count) {
return nil;
}

NSMutableArray *actions = [NSMutableArray array];
for (NSString *action in _accessibilityActions) {
[actions addObject:[[UIAccessibilityCustomAction alloc] initWithName:action
target:self
selector:@selector(didActivateAccessibilityCustomAction:)]];
}

return [actions copy];
}

- (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)action
{
if (!_onAccessibilityAction) {
return NO;
}

_onAccessibilityAction(@{
@"action": action.name,
@"target": self.reactTag
});

return YES;
}

- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
{
_pointerEvents = pointerEvents;
Expand Down
2 changes: 2 additions & 0 deletions React/Views/RCTViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio

// Acessibility related properties
RCT_REMAP_VIEW_PROPERTY(accessible, reactAccessibilityElement.isAccessibilityElement, BOOL)
RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, reactAccessibilityElement.accessibilityLabel, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityTraits, reactAccessibilityElement.accessibilityTraits, UIAccessibilityTraits)
RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL)
RCT_REMAP_VIEW_PROPERTY(onAccessibilityAction, reactAccessibilityElement.onAccessibilityAction, RCTDirectEventBlock)
RCT_REMAP_VIEW_PROPERTY(onAccessibilityTap, reactAccessibilityElement.onAccessibilityTap, RCTDirectEventBlock)
RCT_REMAP_VIEW_PROPERTY(onMagicTap, reactAccessibilityElement.onMagicTap, RCTDirectEventBlock)
RCT_REMAP_VIEW_PROPERTY(testID, reactAccessibilityElement.accessibilityIdentifier, NSString)
Expand Down

0 comments on commit 36ad813

Please sign in to comment.