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));
/**
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
node provided directly to the function call - only on all descendants.

View File

@@ -10,6 +10,8 @@
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import <queue>
extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *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))
{
for (ASDisplayNode *subnode in node.subnodes) {

View File

@@ -8,23 +8,27 @@
#import "_ASDisplayViewAccessiblity.h"
#import "_ASDisplayView.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import <objc/runtime.h>
#import <queue>
#pragma mark - UIAccessibilityElement
static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
@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
{
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.accessibilityLabel = node.accessibilityLabel;
self.accessibilityHint = node.accessibilityHint;
@@ -34,13 +38,17 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
- (ASDisplayNode *)asyncdisplaykit_node
{
return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey);
return objc_getAssociatedObject(self, @selector(asyncdisplaykit_node));
}
@end
#pragma mark - _ASDisplayView
#pragma mark - _ASDisplayView / UIAccessibilityContainer
static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) {
return (!node.isAccessibilityElement && [node accessibilityElementCount] > 0);
}
@interface _ASDisplayView () {
NSMutableArray *_accessibleElements;
@@ -65,41 +73,29 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
if (selfNode.shouldRasterizeDescendants) {
// 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
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion.
std::queue<ASDisplayNode *> queue;
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
ASDisplayNodePerformBlockOnEveryNodeBFS(selfNode, ^(ASDisplayNode * _Nonnull node) {
// For every subnode we have to create a UIAccessibilityElement as we cannot just add the view to the
// accessibleElements as for a subnode of a node with shouldRasterizeDescendants enabled no view exists
if (node != selfNode && node.isAccessibilityElement) {
UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
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]);
[_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:node]];
}
});
return _accessibleElements;
}
// Handle not rasterize case
// Create UI accessiblity elements for each subnode that represent an elment within the accessibility container
for (ASDisplayNode *subnode in selfNode.subnodes) {
// Check if this subnode is a UIAccessibilityContainer
if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) {
// We are good and the view is an UIAccessibilityContainer so add it
if (subnode.isAccessibilityElement) {
if (subnode.isLayerBacked) {
// 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];
} 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
{
ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created.");
if (_accessibleElements == nil) {
return nil;
}