Add accessibilityHint for iOS (#18093)

Summary:
This adds the accessibilityHint for View, Text and Touchable* on iOS.
The accessibilityHint provides some more information about an element
when the accessibilityLabel is not enough.

The accessibilityHint is a core accessibility property on iOS.

From https://developer.apple.com/documentation/objectivec/nsobject/1615093-accessibilityhint:
> An accessibility hint helps users understand what will happen when they perform an action on the accessibility element when that result is not obvious from the accessibility label.

Related issue: https://github.com/facebook/react-native/issues/14706

The npm scripts `test`, `flow`, `lint` and `prettier` are satisfied.

I added a couple of examples to the RNTester app. The Accessibility Inspector on Mac helps debugging accessibility stuff on a simulator, but it does not show the accessibilityHint. Therefore I tested the RNTester app on an iPhone 8 device using VoiceOver to verify the hint functionality. It works fine, and I've tested disabling and enabling "read hints" in the VoiceOver settings on the phone.

https://github.com/facebook/react-native-website/pull/222

[IOS][FEATURE][Accessibility] - Add accessibilityHint for View, Text, Touchable* on iOS
Closes https://github.com/facebook/react-native/pull/18093

Reviewed By: hramos

Differential Revision: D7230780

Pulled By: ziqichen6

fbshipit-source-id: 172ad28dc9ae2b67ea256100f6acb939f2466d0b
This commit is contained in:
Mats Byrkeland 2018-07-25 17:33:58 -07:00 committed by Facebook Github Bot
parent b7bb25fe4c
commit 253b29dbd8
9 changed files with 47 additions and 2 deletions

View File

@ -160,6 +160,7 @@ const TouchableBounce = ((createReactClass({
style={[{transform: [{scale: this.state.scale}]}, this.props.style]} style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
accessible={this.props.accessible !== false} accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel} accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
accessibilityComponentType={this.props.accessibilityComponentType} accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits} accessibilityTraits={this.props.accessibilityTraits}
nativeID={this.props.nativeID} nativeID={this.props.nativeID}

View File

@ -348,6 +348,7 @@ const TouchableHighlight = ((createReactClass({
<View <View
accessible={this.props.accessible !== false} accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel} accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
accessibilityComponentType={this.props.accessibilityComponentType} accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits} accessibilityTraits={this.props.accessibilityTraits}
style={StyleSheet.compose( style={StyleSheet.compose(

View File

@ -256,6 +256,7 @@ const TouchableOpacity = ((createReactClass({
<Animated.View <Animated.View
accessible={this.props.accessible !== false} accessible={this.props.accessible !== false}
accessibilityLabel={this.props.accessibilityLabel} accessibilityLabel={this.props.accessibilityLabel}
accessibilityHint={this.props.accessibilityHint}
accessibilityComponentType={this.props.accessibilityComponentType} accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits} accessibilityTraits={this.props.accessibilityTraits}
style={[this.props.style, {opacity: this.state.anim}]} style={[this.props.style, {opacity: this.state.anim}]}

View File

@ -43,6 +43,7 @@ export type Props = $ReadOnly<{|
| string | string
| Array<any> | Array<any>
| any, | any,
accessibilityHint?: string,
accessibilityTraits?: ?AccessibilityTraitsFlow, accessibilityTraits?: ?AccessibilityTraitsFlow,
children?: ?React.Node, children?: ?React.Node,
delayLongPress?: ?number, delayLongPress?: ?number,
@ -75,6 +76,7 @@ const TouchableWithoutFeedback = ((createReactClass({
propTypes: { propTypes: {
accessible: PropTypes.bool, accessible: PropTypes.bool,
accessibilityLabel: PropTypes.node, accessibilityLabel: PropTypes.node,
accessibilityHint: PropTypes.string,
accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentTypes), accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentTypes),
accessibilityTraits: PropTypes.oneOfType([ accessibilityTraits: PropTypes.oneOfType([
PropTypes.oneOf(AccessibilityTraits), PropTypes.oneOf(AccessibilityTraits),
@ -238,6 +240,7 @@ const TouchableWithoutFeedback = ((createReactClass({
return (React: any).cloneElement(child, { return (React: any).cloneElement(child, {
accessible: this.props.accessible !== false, accessible: this.props.accessible !== false,
accessibilityLabel: this.props.accessibilityLabel, accessibilityLabel: this.props.accessibilityLabel,
accessibilityHint: this.props.accessibilityHint,
accessibilityComponentType: this.props.accessibilityComponentType, accessibilityComponentType: this.props.accessibilityComponentType,
accessibilityTraits: this.props.accessibilityTraits, accessibilityTraits: this.props.accessibilityTraits,
nativeID: this.props.nativeID, nativeID: this.props.nativeID,

View File

@ -24,6 +24,7 @@ ReactNativeViewAttributes.UIView = {
accessibilityRole: true, accessibilityRole: true,
accessibilityStates: true, accessibilityStates: true,
accessibilityTraits: true, accessibilityTraits: true,
accessibilityHint: true,
importantForAccessibility: true, importantForAccessibility: true,
nativeID: true, nativeID: true,
testID: true, testID: true,

View File

@ -87,6 +87,7 @@ export type ViewProps = $ReadOnly<{|
| string | string
| Array<any> | Array<any>
| any, | any,
accessibilityHint?: string,
accessibilityActions?: Array<string>, accessibilityActions?: Array<string>,
accessibilityComponentType?: AccessibilityComponentType, accessibilityComponentType?: AccessibilityComponentType,
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive', accessibilityLiveRegion?: 'none' | 'polite' | 'assertive',
@ -128,6 +129,17 @@ module.exports = {
*/ */
accessibilityLabel: PropTypes.node, accessibilityLabel: PropTypes.node,
/**
* An accessibility hint helps users understand what will happen when they perform
* an action on the accessibility element when that result is not obvious from the
* accessibility label.
*
* @platform ios
*
* See http://facebook.github.io/react-native/docs/view.html#accessibilityHint
*/
accessibilityHint: PropTypes.string,
/** /**
* Provides an array of custom actions available for accessibility. * Provides an array of custom actions available for accessibility.
* *

View File

@ -12,7 +12,7 @@
var React = require('react'); var React = require('react');
var ReactNative = require('react-native'); var ReactNative = require('react-native');
var {AccessibilityInfo, Text, View} = ReactNative; var {AccessibilityInfo, Text, View, TouchableOpacity} = ReactNative;
class AccessibilityIOSExample extends React.Component<{}> { class AccessibilityIOSExample extends React.Component<{}> {
render() { render() {
@ -39,6 +39,31 @@ class AccessibilityIOSExample extends React.Component<{}> {
<Text accessibilityLabel="Test of accessibilityLabel" accessible={true}> <Text accessibilityLabel="Test of accessibilityLabel" accessible={true}>
This text component's accessibilityLabel is set explicitly. This text component's accessibilityLabel is set explicitly.
</Text> </Text>
<View
accessibilityLabel="Test of accessibilityHint"
accessibilityHint="The hint provides more info than the label does"
accessible={true}>
<Text>
This view component has both an accessibilityLabel and an
accessibilityHint explicitly set.
</Text>
</View>
<Text
accessibilityLabel="Test of accessibilityHint"
accessibilityHint="The hint provides more info than the label does">
This text component has both an accessibilityLabel and an
accessibilityHint explicitly set.
</Text>
<TouchableOpacity
accessibilityLabel="Test of accessibilityHint"
accessibilityHint="The hint provides more info than the label does">
<View>
<Text>
This button has both an accessibilityLabel and an
accessibilityHint explicitly set.
</Text>
</View>
</TouchableOpacity>
<View accessibilityElementsHidden={true}> <View accessibilityElementsHidden={true}>
<Text> <Text>
This view's children are hidden from the accessibility tree This view's children are hidden from the accessibility tree

View File

@ -111,6 +111,7 @@ RCT_EXPORT_VIEW_PROPERTY(nativeID, NSString)
RCT_REMAP_VIEW_PROPERTY(accessible, reactAccessibilityElement.isAccessibilityElement, BOOL) RCT_REMAP_VIEW_PROPERTY(accessible, reactAccessibilityElement.isAccessibilityElement, BOOL)
RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSString) RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, reactAccessibilityElement.accessibilityLabel, NSString) RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, reactAccessibilityElement.accessibilityLabel, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityHint, reactAccessibilityElement.accessibilityHint, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityTraits, reactAccessibilityElement.accessibilityTraits, UIAccessibilityTraits) RCT_REMAP_VIEW_PROPERTY(accessibilityTraits, reactAccessibilityElement.accessibilityTraits, UIAccessibilityTraits)
RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL) RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL)
RCT_REMAP_VIEW_PROPERTY(accessibilityElementsHidden, reactAccessibilityElement.accessibilityElementsHidden, BOOL) RCT_REMAP_VIEW_PROPERTY(accessibilityElementsHidden, reactAccessibilityElement.accessibilityElementsHidden, BOOL)