Skip to content

Commit 5eabaec

Browse files
joevilchesfacebook-github-bot
authored andcommitted
Allow accessibilityOrder to reference itself (facebook#51004)
Summary: It would be very convenient if `accessibilityOrder` could reference itself. Meaning the View with the `accessibilityOrder` prop can include its own `nativeID` in the array. This makes sense API wise - we allow for referencing parents and their descendants, so long as they are treated as an element and not a container. This is pretty nice since you no longer have to wrap everything in a View who's sole purpose is `accessibilityOrder`. Under the hood things get a bit garbled, however, since iOS only lets you have UIViews that are either accessibility elements or accessibility containers - and we need to support both at the same time for this to work. To do this, we make use of the `UIAccessibilityElement` class and just forward all of the logic to the View with the `accessibilityOrder` prop. This View will also not be an accessibility element from the point of view of iOS. Changelog: [Internal] Differential Revision: D73792934
1 parent 2d3285a commit 5eabaec

File tree

5 files changed

+172
-23
lines changed

5 files changed

+172
-23
lines changed

packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import "RCTParagraphComponentAccessibilityProvider.h"
1010

1111
#import <MobileCoreServices/UTCoreTypes.h>
12+
#import <React/RCTViewAccessibilityElement.h>
1213
#import <react/renderer/components/text/ParagraphComponentDescriptor.h>
1314
#import <react/renderer/components/text/ParagraphProps.h>
1415
#import <react/renderer/components/text/ParagraphState.h>
@@ -212,23 +213,26 @@ - (BOOL)isAccessibilityCoopted
212213
NSMutableSet<UIView *> *cooptingCandidates = [NSMutableSet new];
213214
while (ancestor) {
214215
if ([ancestor isKindOfClass:[RCTViewComponentView class]]) {
216+
if ([((RCTViewComponentView *)ancestor) accessibilityLabelForCoopting]) {
217+
// We found a label above us. That would be coopted before we would be
218+
return NO;
219+
} else if ([((RCTViewComponentView *)ancestor) wantsToCooptLabel]) {
220+
// We found an view that is looking to coopt a label below it
221+
[cooptingCandidates addObject:ancestor];
222+
}
223+
215224
NSArray *elements = ancestor.accessibilityElements;
216225
if ([elements count] > 0 && [cooptingCandidates count] > 0) {
217-
for (UIView *element in elements) {
218-
if ([cooptingCandidates containsObject:element]) {
226+
for (NSObject *element in elements) {
227+
if ([element isKindOfClass:[UIView class]] && [cooptingCandidates containsObject:((UIView *)element)]) {
228+
return YES;
229+
} else if (
230+
[element isKindOfClass:[RCTViewAccessibilityElement class]] &&
231+
[cooptingCandidates containsObject:((RCTViewAccessibilityElement *)element).view]) {
219232
return YES;
220233
}
221234
}
222235
}
223-
224-
if ([((RCTViewComponentView *)ancestor) accessibilityLabelForCoopting]) {
225-
// We found a label above us. That would be coopted before we would be
226-
return NO;
227-
} else if (ancestor.isAccessibilityElement) {
228-
// We found an accessible view without a label for coopting before anything
229-
// else, if it is in some accessibilityElements somewhere then it will coopt
230-
[cooptingCandidates addObject:ancestor];
231-
}
232236
} else if (![ancestor isKindOfClass:[RCTViewComponentView class]] && ancestor.accessibilityLabel) {
233237
// Same as above, for UIView case. Cannot call this on RCTViewComponentView
234238
// as it is recursive and quite expensive.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTViewComponentView.h"
9+
10+
#import <UIKit/UIKit.h>
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
/*
15+
* A UIAcccessibilityElement representing a RCTViewComponentView from an
16+
* accessibility standpoint. This enables RCTViewComponentView's to reference
17+
* themselves in `accessibilityElements` without actually being an accessibility
18+
* element. If it were, then iOS would not call into `accessibilityElements`.
19+
*/
20+
@interface RCTViewAccessibilityElement : UIAccessibilityElement
21+
22+
@property (readonly) RCTViewComponentView *view;
23+
24+
- (instancetype)initWithView:(RCTViewComponentView *)view;
25+
26+
@end
27+
28+
NS_ASSUME_NONNULL_END
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTViewAccessibilityElement.h"
9+
10+
@implementation RCTViewAccessibilityElement
11+
12+
- (instancetype)initWithView:(RCTViewComponentView *)view
13+
{
14+
if (self = [super initWithAccessibilityContainer:view]) {
15+
_view = view;
16+
}
17+
18+
return self;
19+
}
20+
21+
- (CGRect)accessibilityFrame
22+
{
23+
return UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view);
24+
}
25+
26+
#pragma mark - Forwarding to _view
27+
28+
- (NSString *)accessibilityLabel
29+
{
30+
return _view.accessibilityLabel;
31+
}
32+
33+
- (NSString *)accessibilityValue
34+
{
35+
return _view.accessibilityValue;
36+
}
37+
38+
- (UIAccessibilityTraits)accessibilityTraits
39+
{
40+
return _view.accessibilityTraits;
41+
}
42+
43+
- (NSString *)accessibilityHint
44+
{
45+
return _view.accessibilityHint;
46+
}
47+
48+
- (BOOL)accessibilityIgnoresInvertColors
49+
{
50+
return _view.accessibilityIgnoresInvertColors;
51+
}
52+
53+
- (BOOL)shouldGroupAccessibilityChildren
54+
{
55+
return _view.shouldGroupAccessibilityChildren;
56+
}
57+
58+
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
59+
{
60+
return _view.accessibilityCustomActions;
61+
}
62+
63+
- (NSString *)accessibilityLanguage
64+
{
65+
return _view.accessibilityLanguage;
66+
}
67+
68+
- (BOOL)accessibilityViewIsModal
69+
{
70+
return _view.accessibilityViewIsModal;
71+
}
72+
73+
- (BOOL)accessibilityElementsHidden
74+
{
75+
return _view.accessibilityElementsHidden;
76+
}
77+
78+
- (BOOL)accessibilityRespondsToUserInteraction
79+
{
80+
return _view.accessibilityRespondsToUserInteraction;
81+
}
82+
83+
@end

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ NS_ASSUME_NONNULL_BEGIN
8181
*/
8282
- (NSString *)accessibilityLabelForCoopting;
8383

84+
/*
85+
* This View has no label and will look to coopt something below it
86+
*/
87+
- (BOOL)wantsToCooptLabel;
88+
8489
/*
8590
* This is a fragment of temporary workaround that we need only temporary and will get rid of soon.
8691
*/

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#import "RCTViewComponentView.h"
9+
#import "RCTViewAccessibilityElement.h"
910

1011
#import <CoreGraphics/CoreGraphics.h>
1112
#import <QuartzCore/QuartzCore.h>
@@ -49,7 +50,8 @@ @implementation RCTViewComponentView {
4950
NSSet<NSString *> *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN;
5051
UIView *_containerView;
5152
BOOL _useCustomContainerView;
52-
NSMutableArray<NSString *> *_accessibleElementsNativeIds;
53+
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
54+
RCTViewAccessibilityElement *_axElementDescribingSelf;
5355
}
5456

5557
#ifdef RCT_DYNAMIC_FRAMEWORKS
@@ -391,11 +393,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
391393
}
392394
}
393395

396+
// `accessibilityOrder`
394397
if (oldViewProps.accessibilityOrder != newViewProps.accessibilityOrder &&
395398
ReactNativeFeatureFlags::enableAccessibilityOrder()) {
396-
_accessibleElementsNativeIds = [NSMutableArray new];
399+
// Creating a set since a lot of logic requires lookups in here. However,
400+
// we still need to preserve the orginal order. So just read from props
401+
// if need to access that
402+
_accessibilityOrderNativeIDs = [NSMutableSet new];
397403
for (const std::string &childId : newViewProps.accessibilityOrder) {
398-
[_accessibleElementsNativeIds addObject:RCTNSStringFromString(childId)];
404+
[_accessibilityOrderNativeIDs addObject:RCTNSStringFromString(childId)];
399405
}
400406
}
401407

@@ -1141,20 +1147,31 @@ - (NSObject *)accessibilityElement
11411147

11421148
- (NSArray<NSObject *> *)accessibilityElements
11431149
{
1144-
if ([_accessibleElementsNativeIds count] <= 0) {
1150+
if ([_accessibilityOrderNativeIDs count] <= 0) {
11451151
return super.accessibilityElements;
11461152
}
11471153

11481154
NSMutableDictionary<NSString *, UIView *> *nativeIdToView = [NSMutableDictionary new];
1149-
NSSet<NSString *> *nativeIdSet = [[NSSet alloc] initWithArray:_accessibleElementsNativeIds];
11501155

1151-
[RCTViewComponentView collectAccessibilityElements:self intoDictionary:nativeIdToView nativeIds:nativeIdSet];
1152-
1153-
NSMutableArray<UIView *> *elements = [NSMutableArray new];
1154-
for (NSString *childId : _accessibleElementsNativeIds) {
1155-
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:childId];
1156-
if (viewWithMatchingNativeId) {
1157-
[elements addObject:viewWithMatchingNativeId];
1156+
[RCTViewComponentView collectAccessibilityElements:self
1157+
intoDictionary:nativeIdToView
1158+
nativeIds:_accessibilityOrderNativeIDs];
1159+
1160+
NSMutableArray<NSObject *> *elements = [NSMutableArray new];
1161+
for (auto childId : _props->accessibilityOrder) {
1162+
NSString *nsStringChildId = RCTNSStringFromString(childId);
1163+
// Special case to allow for self-referencing with accessibilityOrder
1164+
if (nsStringChildId == self.nativeId) {
1165+
if (!_axElementDescribingSelf) {
1166+
_axElementDescribingSelf = [[RCTViewAccessibilityElement alloc] initWithView:self];
1167+
}
1168+
_axElementDescribingSelf.isAccessibilityElement = [super isAccessibilityElement];
1169+
[elements addObject:_axElementDescribingSelf];
1170+
} else {
1171+
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
1172+
if (viewWithMatchingNativeId) {
1173+
[elements addObject:viewWithMatchingNativeId];
1174+
}
11581175
}
11591176
}
11601177

@@ -1211,12 +1228,24 @@ - (NSString *)accessibilityLabelForCoopting
12111228
return super.accessibilityLabel;
12121229
}
12131230

1231+
- (BOOL)wantsToCooptLabel
1232+
{
1233+
return !super.accessibilityLabel && super.isAccessibilityElement;
1234+
}
1235+
12141236
- (BOOL)isAccessibilityElement
12151237
{
12161238
if (self.contentView != nil) {
12171239
return self.contentView.isAccessibilityElement;
12181240
}
12191241

1242+
// If we reference ourselves in accessibilityOrder then we will make a
1243+
// UIAccessibilityElement object to represent ourselves since returning YES
1244+
// here would mean iOS would not call into accessibilityElements
1245+
if ([_accessibilityOrderNativeIDs containsObject:self.nativeId]) {
1246+
return NO;
1247+
}
1248+
12201249
return [super isAccessibilityElement];
12211250
}
12221251

0 commit comments

Comments
 (0)