-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. #468
Merged
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,9 +23,21 @@ | |
#import <AsyncDisplayKit/ASDisplayNode+Beta.h> | ||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h> | ||
|
||
#import <queue> | ||
|
||
NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() { | ||
return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton; | ||
} | ||
|
||
#pragma mark - UIAccessibilityElement | ||
|
||
typedef NSComparisonResult (^SortAccessibilityElementsComparator)(UIAccessibilityElement *, UIAccessibilityElement *); | ||
@protocol ASAccessibilityElementPositioning | ||
|
||
@property (nonatomic, readonly) CGRect accessibilityFrame; | ||
|
||
@end | ||
|
||
typedef NSComparisonResult (^SortAccessibilityElementsComparator)(id<ASAccessibilityElementPositioning>, id<ASAccessibilityElementPositioning>); | ||
|
||
/// Sort accessiblity elements first by y and than by x origin. | ||
static void SortAccessibilityElements(NSMutableArray *elements) | ||
|
@@ -35,7 +47,7 @@ static void SortAccessibilityElements(NSMutableArray *elements) | |
static SortAccessibilityElementsComparator comparator = nil; | ||
static dispatch_once_t onceToken; | ||
dispatch_once(&onceToken, ^{ | ||
comparator = ^NSComparisonResult(UIAccessibilityElement *a, UIAccessibilityElement *b) { | ||
comparator = ^NSComparisonResult(id<ASAccessibilityElementPositioning> a, id<ASAccessibilityElementPositioning> b) { | ||
CGPoint originA = a.accessibilityFrame.origin; | ||
CGPoint originB = b.accessibilityFrame.origin; | ||
if (originA.y == originB.y) { | ||
|
@@ -50,7 +62,7 @@ static void SortAccessibilityElements(NSMutableArray *elements) | |
[elements sortUsingComparator:comparator]; | ||
} | ||
|
||
@interface ASAccessibilityElement : UIAccessibilityElement | ||
@interface ASAccessibilityElement : UIAccessibilityElement<ASAccessibilityElementPositioning> | ||
|
||
@property (nonatomic, strong) ASDisplayNode *node; | ||
@property (nonatomic, strong) ASDisplayNode *containerNode; | ||
|
@@ -85,6 +97,25 @@ - (CGRect)accessibilityFrame | |
|
||
#pragma mark - _ASDisplayView / UIAccessibilityContainer | ||
|
||
@interface ASAccessibilityCustomAction : UIAccessibilityCustomAction<ASAccessibilityElementPositioning> | ||
|
||
@property (nonatomic, strong) UIView *container; | ||
@property (nonatomic, strong) ASDisplayNode *node; | ||
@property (nonatomic, strong) ASDisplayNode *containerNode; | ||
|
||
@end | ||
|
||
@implementation ASAccessibilityCustomAction | ||
|
||
- (CGRect)accessibilityFrame | ||
{ | ||
CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node]; | ||
accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.container); | ||
return accessibilityFrame; | ||
} | ||
|
||
@end | ||
|
||
/// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container | ||
static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements) | ||
{ | ||
|
@@ -100,12 +131,64 @@ static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplay | |
}); | ||
} | ||
|
||
static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, _ASDisplayView *view, NSMutableArray *elements) { | ||
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:container containerNode:container]; | ||
|
||
NSMutableArray<ASAccessibilityElement *> *labeledNodes = [NSMutableArray array]; | ||
NSMutableArray<ASAccessibilityCustomAction *> *actions = [NSMutableArray array]; | ||
std::queue<ASDisplayNode *> queue; | ||
queue.push(container); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Somebody should build an NSFastEnumeration-conforming class that takes a BFS/DFS option, a collection, and a |
||
|
||
ASDisplayNode *node; | ||
while (!queue.empty()) { | ||
node = queue.front(); | ||
queue.pop(); | ||
|
||
if (node != container && node.isAccessibilityContainer) { | ||
CollectAccessibilityElementsForContainer(node, view, elements); | ||
continue; | ||
} | ||
|
||
if (node.accessibilityLabel.length > 0) { | ||
if (node.accessibilityTraits & InteractiveAccessibilityTraitsMask()) { | ||
ASAccessibilityCustomAction *action = [[ASAccessibilityCustomAction alloc] initWithName:node.accessibilityLabel target:node selector:@selector(performAccessibilityCustomAction:)]; | ||
action.node = node; | ||
action.containerNode = node.supernode; | ||
action.container = node.supernode.view; | ||
[actions addObject:action]; | ||
} else { | ||
// Even though not surfaced to UIKit, create a non-interactive element for purposes of building sorted aggregated label. | ||
ASAccessibilityElement *nonInteractiveElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:node containerNode:container]; | ||
[labeledNodes addObject:nonInteractiveElement]; | ||
} | ||
} | ||
|
||
for (ASDisplayNode *subnode in node.subnodes) { | ||
queue.push(subnode); | ||
} | ||
} | ||
|
||
SortAccessibilityElements(labeledNodes); | ||
NSArray *labels = [labeledNodes valueForKey:@"accessibilityLabel"]; | ||
accessiblityElement.accessibilityLabel = [labels componentsJoinedByString:@", "]; | ||
|
||
SortAccessibilityElements(actions); | ||
accessiblityElement.accessibilityCustomActions = actions; | ||
|
||
[elements addObject:accessiblityElement]; | ||
} | ||
|
||
/// Collect all accessibliity elements for a given view and view node | ||
static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableArray *elements) | ||
{ | ||
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); | ||
|
||
ASDisplayNode *node = view.asyncdisplaykit_node; | ||
|
||
if (node.isAccessibilityContainer) { | ||
CollectAccessibilityElementsForContainer(node, view, elements); | ||
return; | ||
} | ||
|
||
// Handle rasterize case | ||
if (node.rasterizesSubtree) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just acknowledging that while this is not Objective-C convention (having
is
in the property name), this follows the UIAccessibility convention so good call.Should we use objective-C atomics here? Not a big deal either way.