Skip to content

Commit c9cd8d7

Browse files
authored
[iOS] Pass accessibility props to button view (#3444)
## Description Currently adding `accessibility` props to `Pressable` component doesn't work. It happens because on `fabric` those props are assigned to `RNGestureHandlerButtonComponentView` instead of `RNGestureHandlerButton`. This PR adds [logic from react-native](https://github.com/facebook/react-native/blob/b226049a4a28ea3f7f32266269fb76340c324d42/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L343) to assign props to `_buttonView` instead of `ComponentView`. Fixes #3428 >[!NOTE] > I've skipped one part of this logic, namely [accessibilityElements](https://github.com/facebook/react-native/blob/b226049a4a28ea3f7f32266269fb76340c324d42/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L394C2-L399C4) because of type errors (compiler says that `accessibilityElements` doesn't exist on `props`), but it shouldn't change the behavior. ## Test plan <details> <summary>Tested on the following example:</summary> ```jsx import React from 'react'; import { StyleSheet, Text, View, Pressable as RNPressable } from 'react-native'; import { GestureHandlerRootView, RectButton, Pressable, } from 'react-native-gesture-handler'; // Not accessible: const NotAccessibleButton = () => ( <RectButton style={styles.button} onPress={() => console.log('Not accessible')}> <Text>Foo</Text> </RectButton> ); // Accessible: const AccessibleButton = () => ( <RectButton style={styles.button} onPress={() => console.log('Accessible')}> <View accessible accessibilityRole="button"> <Text>Bar</Text> </View> </RectButton> ); export default function EmptyExample() { return ( <GestureHandlerRootView style={styles.container}> <NotAccessibleButton /> <AccessibleButton /> <Pressable style={styles.button} accessible testID="RNGH" accessibilityLabel="RNGH" onPress={() => console.log('RNGH')}> <Text>Pressable</Text> </Pressable> <RNPressable style={styles.button} accessible accessibilityLabel="RN" onPress={() => console.log('RN')}> <Text>RNPressable</Text> </RNPressable> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', gap: 20, }, button: { width: 100, height: 25, backgroundColor: 'crimson', alignItems: 'center', justifyContent: 'space-around', }, }); ``` </details>
1 parent 33d22f8 commit c9cd8d7

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

apple/RNGestureHandlerButtonComponentView.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
NS_ASSUME_NONNULL_BEGIN
1414

1515
@interface RNGestureHandlerButtonComponentView : RCTViewComponentView
16-
16+
- (void)setAccessibilityProps:(const facebook::react::Props::Shared &)props
17+
oldProps:(const facebook::react::Props::Shared &)oldProps;
1718
@end
1819

1920
NS_ASSUME_NONNULL_END

apple/RNGestureHandlerButtonComponentView.mm

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,108 @@ + (ComponentDescriptorProvider)componentDescriptorProvider
101101
return concreteComponentDescriptorProvider<RNGestureHandlerButtonComponentDescriptor>();
102102
}
103103

104+
#if TARGET_OS_IOS
105+
// Taken from
106+
// https://github.com/facebook/react-native/blob/b226049a4a28ea3f7f32266269fb76340c324d42/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L343
107+
- (void)setAccessibilityProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
108+
{
109+
const auto &oldButtonProps = *std::static_pointer_cast<const RNGestureHandlerButtonProps>(oldProps);
110+
const auto &newButtonProps = *std::static_pointer_cast<const RNGestureHandlerButtonProps>(props);
111+
112+
if (!oldProps || oldButtonProps.accessible != newButtonProps.accessible) {
113+
_buttonView.isAccessibilityElement = newButtonProps.accessible;
114+
}
115+
116+
if (!oldProps || oldButtonProps.accessibilityLabel != newButtonProps.accessibilityLabel) {
117+
_buttonView.accessibilityLabel = RCTNSStringFromStringNilIfEmpty(newButtonProps.accessibilityLabel);
118+
}
119+
120+
if (!oldProps || oldButtonProps.accessibilityLanguage != newButtonProps.accessibilityLanguage) {
121+
_buttonView.accessibilityLanguage = RCTNSStringFromStringNilIfEmpty(newButtonProps.accessibilityLanguage);
122+
}
123+
124+
if (!oldProps || oldButtonProps.accessibilityHint != newButtonProps.accessibilityHint) {
125+
_buttonView.accessibilityHint = RCTNSStringFromStringNilIfEmpty(newButtonProps.accessibilityHint);
126+
}
127+
128+
if (!oldProps || oldButtonProps.accessibilityViewIsModal != newButtonProps.accessibilityViewIsModal) {
129+
_buttonView.accessibilityViewIsModal = newButtonProps.accessibilityViewIsModal;
130+
}
131+
132+
if (!oldProps || oldButtonProps.accessibilityElementsHidden != newButtonProps.accessibilityElementsHidden) {
133+
_buttonView.accessibilityElementsHidden = newButtonProps.accessibilityElementsHidden;
134+
}
135+
136+
if (!oldProps ||
137+
oldButtonProps.accessibilityShowsLargeContentViewer != newButtonProps.accessibilityShowsLargeContentViewer) {
138+
if (@available(iOS 13.0, *)) {
139+
if (newButtonProps.accessibilityShowsLargeContentViewer) {
140+
_buttonView.showsLargeContentViewer = YES;
141+
UILargeContentViewerInteraction *interaction = [[UILargeContentViewerInteraction alloc] init];
142+
[_buttonView addInteraction:interaction];
143+
} else {
144+
_buttonView.showsLargeContentViewer = NO;
145+
}
146+
}
147+
}
148+
149+
if (!oldProps || oldButtonProps.accessibilityLargeContentTitle != newButtonProps.accessibilityLargeContentTitle) {
150+
if (@available(iOS 13.0, *)) {
151+
_buttonView.largeContentTitle = RCTNSStringFromStringNilIfEmpty(newButtonProps.accessibilityLargeContentTitle);
152+
}
153+
}
154+
155+
if (!oldProps || oldButtonProps.accessibilityTraits != newButtonProps.accessibilityTraits) {
156+
_buttonView.accessibilityTraits =
157+
RCTUIAccessibilityTraitsFromAccessibilityTraits(newButtonProps.accessibilityTraits);
158+
}
159+
160+
if (!oldProps || oldButtonProps.accessibilityState != newButtonProps.accessibilityState) {
161+
_buttonView.accessibilityTraits &= ~(UIAccessibilityTraitNotEnabled | UIAccessibilityTraitSelected);
162+
const auto accessibilityState = newButtonProps.accessibilityState.value_or(AccessibilityState{});
163+
if (accessibilityState.selected) {
164+
_buttonView.accessibilityTraits |= UIAccessibilityTraitSelected;
165+
}
166+
if (accessibilityState.disabled) {
167+
_buttonView.accessibilityTraits |= UIAccessibilityTraitNotEnabled;
168+
}
169+
}
170+
171+
if (!oldProps || oldButtonProps.accessibilityIgnoresInvertColors != newButtonProps.accessibilityIgnoresInvertColors) {
172+
_buttonView.accessibilityIgnoresInvertColors = newButtonProps.accessibilityIgnoresInvertColors;
173+
}
174+
175+
if (!oldProps || oldButtonProps.accessibilityValue != newButtonProps.accessibilityValue) {
176+
if (newButtonProps.accessibilityValue.text.has_value()) {
177+
_buttonView.accessibilityValue = RCTNSStringFromStringNilIfEmpty(newButtonProps.accessibilityValue.text.value());
178+
} else if (
179+
newButtonProps.accessibilityValue.now.has_value() && newButtonProps.accessibilityValue.min.has_value() &&
180+
newButtonProps.accessibilityValue.max.has_value()) {
181+
CGFloat val = (CGFloat)(newButtonProps.accessibilityValue.now.value()) /
182+
(newButtonProps.accessibilityValue.max.value() - newButtonProps.accessibilityValue.min.value());
183+
_buttonView.accessibilityValue = [NSNumberFormatter localizedStringFromNumber:@(val)
184+
numberStyle:NSNumberFormatterPercentStyle];
185+
;
186+
} else {
187+
_buttonView.accessibilityValue = nil;
188+
}
189+
}
190+
191+
if (!oldProps || oldButtonProps.testId != newButtonProps.testId) {
192+
UIView *accessibilityView = (UIView *)_buttonView;
193+
accessibilityView.accessibilityIdentifier = RCTNSStringFromString(newButtonProps.testId);
194+
}
195+
}
196+
#endif
197+
104198
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
105199
{
106200
const auto &newProps = *std::static_pointer_cast<const RNGestureHandlerButtonProps>(props);
107201

108202
_buttonView.userEnabled = newProps.enabled;
109203
#if !TARGET_OS_TV && !TARGET_OS_OSX
110204
_buttonView.exclusiveTouch = newProps.exclusive;
205+
[self setAccessibilityProps:props oldProps:oldProps];
111206
#endif
112207
_buttonView.hitTestEdgeInsets = UIEdgeInsetsMake(
113208
-newProps.hitSlop.top, -newProps.hitSlop.left, -newProps.hitSlop.bottom, -newProps.hitSlop.right);

0 commit comments

Comments
 (0)