Improve _ASDisplayViewAccessibility

- Add class method to create a UIAccessibilityElement from a ASDisplayNode
- Add function to iterate through a ASDisplayNode tree in bfs fashion
- Add assert for _accessibleElements in accessibilityElementAtIndex:
This commit is contained in:
Michael Schneider
2016-04-09 16:04:08 -07:00
parent 14ca529911
commit 1b7db082dd
3 changed files with 57 additions and 34 deletions

View File

@@ -87,6 +87,12 @@ extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node);
*/ */
extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node));
/**
This function will walk the node hierarchy in a breadth first fashion. It does run the block on the node provided
directly to the function call.
*/
extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node));
/** /**
Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the
node provided directly to the function call - only on all descendants. node provided directly to the function call - only on all descendants.

View File

@@ -10,6 +10,8 @@
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import <queue>
extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window) extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
{ {
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window"); ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
@@ -63,6 +65,24 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *
} }
} }
extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
{
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion.
std::queue<ASDisplayNode *> queue;
queue.push(node);
while (!queue.empty()) {
node = queue.front();
queue.pop();
block(node);
// Add all subnodes to process in next step
for (int i = 0; i < node.subnodes.count; i++)
queue.push(node.subnodes[i]);
}
}
extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
{ {
for (ASDisplayNode *subnode in node.subnodes) { for (ASDisplayNode *subnode in node.subnodes) {

View File

@@ -8,23 +8,27 @@
#import "_ASDisplayViewAccessiblity.h" #import "_ASDisplayViewAccessiblity.h"
#import "_ASDisplayView.h" #import "_ASDisplayView.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import <objc/runtime.h> #import <objc/runtime.h>
#import <queue>
#pragma mark - UIAccessibilityElement #pragma mark - UIAccessibilityElement
static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
@implementation UIAccessibilityElement (_ASDisplayView) @implementation UIAccessibilityElement (_ASDisplayView)
+ (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node
{
UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
accessibilityElement.asyncdisplaykit_node = node;
return accessibilityElement;
}
- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node - (void)setAsyncdisplaykit_node:(ASDisplayNode *)node
{ {
objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. objc_setAssociatedObject(self, @selector(asyncdisplaykit_node), node, OBJC_ASSOCIATION_ASSIGN);
// Update UIAccessibilityElement properties from node
self.accessibilityIdentifier = node.accessibilityIdentifier; self.accessibilityIdentifier = node.accessibilityIdentifier;
self.accessibilityLabel = node.accessibilityLabel; self.accessibilityLabel = node.accessibilityLabel;
self.accessibilityHint = node.accessibilityHint; self.accessibilityHint = node.accessibilityHint;
@@ -34,13 +38,17 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
- (ASDisplayNode *)asyncdisplaykit_node - (ASDisplayNode *)asyncdisplaykit_node
{ {
return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); return objc_getAssociatedObject(self, @selector(asyncdisplaykit_node));
} }
@end @end
#pragma mark - _ASDisplayView #pragma mark - _ASDisplayView / UIAccessibilityContainer
static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) {
return (!node.isAccessibilityElement && [node accessibilityElementCount] > 0);
}
@interface _ASDisplayView () { @interface _ASDisplayView () {
NSMutableArray *_accessibleElements; NSMutableArray *_accessibleElements;
@@ -65,41 +73,29 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
if (selfNode.shouldRasterizeDescendants) { if (selfNode.shouldRasterizeDescendants) {
// In this case we have to go through the whole subnodes tree in BFS fashion and create all // In this case we have to go through the whole subnodes tree in BFS fashion and create all
// accessibility elements ourselves as the view hierarchy is flattened // accessibility elements ourselves as the view hierarchy is flattened
ASDisplayNodePerformBlockOnEveryNodeBFS(selfNode, ^(ASDisplayNode * _Nonnull node) {
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion. // For every subnode we have to create a UIAccessibilityElement as we cannot just add the view to the
std::queue<ASDisplayNode *> queue; // accessibleElements as for a subnode of a node with shouldRasterizeDescendants enabled no view exists
queue.push(selfNode);
while (!queue.empty()) {
ASDisplayNode *node = queue.front();
queue.pop();
// Check if we have to add the node to the accessiblity nodes as it's an accessiblity element
if (node != selfNode && node.isAccessibilityElement) { if (node != selfNode && node.isAccessibilityElement) {
UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; [_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:node]];
accessibilityElement.asyncdisplaykit_node = node;
[_accessibleElements addObject:accessibilityElement];
}
// Add all subnodes to process in next step
for (int i = 0; i < node.subnodes.count; i++)
queue.push(node.subnodes[i]);
} }
});
return _accessibleElements; return _accessibleElements;
} }
// Handle not rasterize case // Handle not rasterize case
// Create UI accessiblity elements for each subnode that represent an elment within the accessibility container // Create UI accessiblity elements for each subnode that represent an elment within the accessibility container
for (ASDisplayNode *subnode in selfNode.subnodes) { for (ASDisplayNode *subnode in selfNode.subnodes) {
// Check if this subnode is a UIAccessibilityContainer if (subnode.isAccessibilityElement) {
if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { if (subnode.isLayerBacked) {
// We are good and the view is an UIAccessibilityContainer so add it // The same comment for layer backed subnodes is true as for subnodes within a shouldRasterizeDescendants node.
// See details above
[_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]];
} else {
[_accessibleElements addObject:subnode.view];
}
} else if (ASNodeIsAccessiblityContainer(subnode)) {
[_accessibleElements addObject:subnode.view]; [_accessibleElements addObject:subnode.view];
} else if (subnode.isAccessibilityElement) {
// Create a accessiblity element from the subnode
UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
accessibilityElement.asyncdisplaykit_node = subnode;
[_accessibleElements addObject:accessibilityElement];
} }
} }
@@ -113,6 +109,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
- (id)accessibilityElementAtIndex:(NSInteger)index - (id)accessibilityElementAtIndex:(NSInteger)index
{ {
ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created.");
if (_accessibleElements == nil) { if (_accessibleElements == nil) {
return nil; return nil;
} }