mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Allow accessibilityOrder to reference itself (#51004)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51004 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] Reviewed By: jorge-cab Differential Revision: D73792934 fbshipit-source-id: b0810277c8e410319639b863b59e4e60782bffca
This commit is contained in:
committed by
Facebook GitHub Bot
parent
62742e21c9
commit
4d8eeb34f9
+16
-12
@@ -9,6 +9,7 @@
|
||||
#import "RCTParagraphComponentAccessibilityProvider.h"
|
||||
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <React/RCTViewAccessibilityElement.h>
|
||||
#import <react/renderer/components/text/ParagraphComponentDescriptor.h>
|
||||
#import <react/renderer/components/text/ParagraphProps.h>
|
||||
#import <react/renderer/components/text/ParagraphState.h>
|
||||
@@ -212,23 +213,26 @@ using namespace facebook::react;
|
||||
NSMutableSet<UIView *> *cooptingCandidates = [NSMutableSet new];
|
||||
while (ancestor) {
|
||||
if ([ancestor isKindOfClass:[RCTViewComponentView class]]) {
|
||||
NSArray *elements = ancestor.accessibilityElements;
|
||||
if ([elements count] > 0 && [cooptingCandidates count] > 0) {
|
||||
for (UIView *element in elements) {
|
||||
if ([cooptingCandidates containsObject:element]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([((RCTViewComponentView *)ancestor) accessibilityLabelForCoopting]) {
|
||||
// We found a label above us. That would be coopted before we would be
|
||||
return NO;
|
||||
} else if (ancestor.isAccessibilityElement) {
|
||||
// We found an accessible view without a label for coopting before anything
|
||||
// else, if it is in some accessibilityElements somewhere then it will coopt
|
||||
} else if ([((RCTViewComponentView *)ancestor) wantsToCooptLabel]) {
|
||||
// We found an view that is looking to coopt a label below it
|
||||
[cooptingCandidates addObject:ancestor];
|
||||
}
|
||||
|
||||
NSArray *elements = ancestor.accessibilityElements;
|
||||
if ([elements count] > 0 && [cooptingCandidates count] > 0) {
|
||||
for (NSObject *element in elements) {
|
||||
if ([element isKindOfClass:[UIView class]] && [cooptingCandidates containsObject:((UIView *)element)]) {
|
||||
return YES;
|
||||
} else if (
|
||||
[element isKindOfClass:[RCTViewAccessibilityElement class]] &&
|
||||
[cooptingCandidates containsObject:((RCTViewAccessibilityElement *)element).view]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (![ancestor isKindOfClass:[RCTViewComponentView class]] && ancestor.accessibilityLabel) {
|
||||
// Same as above, for UIView case. Cannot call this on RCTViewComponentView
|
||||
// as it is recursive and quite expensive.
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTViewComponentView.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*
|
||||
* A UIAcccessibilityElement representing a RCTViewComponentView from an
|
||||
* accessibility standpoint. This enables RCTViewComponentView's to reference
|
||||
* themselves in `accessibilityElements` without actually being an accessibility
|
||||
* element. If it were, then iOS would not call into `accessibilityElements`.
|
||||
*/
|
||||
@interface RCTViewAccessibilityElement : UIAccessibilityElement
|
||||
|
||||
@property (readonly) RCTViewComponentView *view;
|
||||
|
||||
- (instancetype)initWithView:(RCTViewComponentView *)view;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTViewAccessibilityElement.h"
|
||||
|
||||
@implementation RCTViewAccessibilityElement
|
||||
|
||||
- (instancetype)initWithView:(RCTViewComponentView *)view
|
||||
{
|
||||
if (self = [super initWithAccessibilityContainer:view]) {
|
||||
_view = view;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGRect)accessibilityFrame
|
||||
{
|
||||
return UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view);
|
||||
}
|
||||
|
||||
#pragma mark - Forwarding to _view
|
||||
|
||||
- (NSString *)accessibilityLabel
|
||||
{
|
||||
return _view.accessibilityLabel;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityValue
|
||||
{
|
||||
return _view.accessibilityValue;
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits
|
||||
{
|
||||
return _view.accessibilityTraits;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityHint
|
||||
{
|
||||
return _view.accessibilityHint;
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityIgnoresInvertColors
|
||||
{
|
||||
return _view.accessibilityIgnoresInvertColors;
|
||||
}
|
||||
|
||||
- (BOOL)shouldGroupAccessibilityChildren
|
||||
{
|
||||
return _view.shouldGroupAccessibilityChildren;
|
||||
}
|
||||
|
||||
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
|
||||
{
|
||||
return _view.accessibilityCustomActions;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLanguage
|
||||
{
|
||||
return _view.accessibilityLanguage;
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityViewIsModal
|
||||
{
|
||||
return _view.accessibilityViewIsModal;
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityElementsHidden
|
||||
{
|
||||
return _view.accessibilityElementsHidden;
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityRespondsToUserInteraction
|
||||
{
|
||||
return _view.accessibilityRespondsToUserInteraction;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -81,6 +81,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (NSString *)accessibilityLabelForCoopting;
|
||||
|
||||
/*
|
||||
* This View has no label and will look to coopt something below it
|
||||
*/
|
||||
- (BOOL)wantsToCooptLabel;
|
||||
|
||||
/*
|
||||
* This is a fragment of temporary workaround that we need only temporary and will get rid of soon.
|
||||
*/
|
||||
|
||||
+40
-11
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#import "RCTViewComponentView.h"
|
||||
#import "RCTViewAccessibilityElement.h"
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
@@ -49,7 +50,8 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
|
||||
NSSet<NSString *> *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN;
|
||||
UIView *_containerView;
|
||||
BOOL _useCustomContainerView;
|
||||
NSMutableArray<NSString *> *_accessibleElementsNativeIds;
|
||||
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
|
||||
RCTViewAccessibilityElement *_axElementDescribingSelf;
|
||||
}
|
||||
|
||||
#ifdef RCT_DYNAMIC_FRAMEWORKS
|
||||
@@ -391,11 +393,15 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// `accessibilityOrder`
|
||||
if (oldViewProps.accessibilityOrder != newViewProps.accessibilityOrder &&
|
||||
ReactNativeFeatureFlags::enableAccessibilityOrder()) {
|
||||
_accessibleElementsNativeIds = [NSMutableArray new];
|
||||
// Creating a set since a lot of logic requires lookups in here. However,
|
||||
// we still need to preserve the orginal order. So just read from props
|
||||
// if need to access that
|
||||
_accessibilityOrderNativeIDs = [NSMutableSet new];
|
||||
for (const std::string &childId : newViewProps.accessibilityOrder) {
|
||||
[_accessibleElementsNativeIds addObject:RCTNSStringFromString(childId)];
|
||||
[_accessibilityOrderNativeIDs addObject:RCTNSStringFromString(childId)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,20 +1147,31 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
|
||||
|
||||
- (NSArray<NSObject *> *)accessibilityElements
|
||||
{
|
||||
if ([_accessibleElementsNativeIds count] <= 0) {
|
||||
if ([_accessibilityOrderNativeIDs count] <= 0) {
|
||||
return super.accessibilityElements;
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSString *, UIView *> *nativeIdToView = [NSMutableDictionary new];
|
||||
NSSet<NSString *> *nativeIdSet = [[NSSet alloc] initWithArray:_accessibleElementsNativeIds];
|
||||
|
||||
[RCTViewComponentView collectAccessibilityElements:self intoDictionary:nativeIdToView nativeIds:nativeIdSet];
|
||||
[RCTViewComponentView collectAccessibilityElements:self
|
||||
intoDictionary:nativeIdToView
|
||||
nativeIds:_accessibilityOrderNativeIDs];
|
||||
|
||||
NSMutableArray<UIView *> *elements = [NSMutableArray new];
|
||||
for (NSString *childId : _accessibleElementsNativeIds) {
|
||||
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:childId];
|
||||
if (viewWithMatchingNativeId) {
|
||||
[elements addObject:viewWithMatchingNativeId];
|
||||
NSMutableArray<NSObject *> *elements = [NSMutableArray new];
|
||||
for (auto childId : _props->accessibilityOrder) {
|
||||
NSString *nsStringChildId = RCTNSStringFromString(childId);
|
||||
// Special case to allow for self-referencing with accessibilityOrder
|
||||
if (nsStringChildId == self.nativeId) {
|
||||
if (!_axElementDescribingSelf) {
|
||||
_axElementDescribingSelf = [[RCTViewAccessibilityElement alloc] initWithView:self];
|
||||
}
|
||||
_axElementDescribingSelf.isAccessibilityElement = [super isAccessibilityElement];
|
||||
[elements addObject:_axElementDescribingSelf];
|
||||
} else {
|
||||
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
|
||||
if (viewWithMatchingNativeId) {
|
||||
[elements addObject:viewWithMatchingNativeId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1211,12 +1228,24 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
return super.accessibilityLabel;
|
||||
}
|
||||
|
||||
- (BOOL)wantsToCooptLabel
|
||||
{
|
||||
return !super.accessibilityLabel && super.isAccessibilityElement;
|
||||
}
|
||||
|
||||
- (BOOL)isAccessibilityElement
|
||||
{
|
||||
if (self.contentView != nil) {
|
||||
return self.contentView.isAccessibilityElement;
|
||||
}
|
||||
|
||||
// If we reference ourselves in accessibilityOrder then we will make a
|
||||
// UIAccessibilityElement object to represent ourselves since returning YES
|
||||
// here would mean iOS would not call into accessibilityElements
|
||||
if ([_accessibilityOrderNativeIDs containsObject:self.nativeId]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super isAccessibilityElement];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user