diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index ebfd1dab21..acd796eeab 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -26,7 +26,7 @@ __unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance. BOOL _inHitTest; BOOL _inPointInside; - NSMutableArray *_accessibleElements; + NSArray *_accessibleElements; } @synthesize asyncdisplaykit_node = _node; diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index 83592db3bb..b08296cee1 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -11,9 +11,6 @@ #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import - - #pragma mark - UIAccessibilityElement @implementation UIAccessibilityElement (_ASDisplayView) @@ -34,10 +31,27 @@ #pragma mark - _ASDisplayView / UIAccessibilityContainer -@interface _ASDisplayView () { - NSMutableArray *_accessibleElements; +static NSArray *ASCollectUIAccessibilityElementsForNode(ASDisplayNode *viewNode, ASDisplayNode *subnode, id container) { + NSMutableArray *accessibleElements = [NSMutableArray array]; + ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull currentNode) { + // For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled + // we have to create a UIAccessibilityElement as no view for this node exists + if (currentNode != viewNode && currentNode.isAccessibilityElement) { + UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:container node:currentNode]; + // As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the + // coordinate system of the supernode + CGRect frame = [viewNode convertRect:currentNode.bounds fromNode:currentNode]; + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, container); + [accessibleElements addObject:accessibilityElement]; + } + }); + + return [accessibleElements copy]; } +@interface _ASDisplayView () { + NSArray *_accessibleElements; +} @end @implementation _ASDisplayView (UIAccessibilityContainer) @@ -46,51 +60,41 @@ - (NSArray *)accessibleElements { - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode == nil) { + ASDisplayNode *viewNode = self.asyncdisplaykit_node; + if (viewNode == nil) { return nil; } - _accessibleElements = [[NSMutableArray alloc] init]; - // Handle rasterize case - if (selfNode.shouldRasterizeDescendants) { - // If the node has shouldRasterizeDescendants enabled it's necessaty to go through the whole subnodes - // tree of the node in BFS fashion and create for all subnodes UIAccessibilityElement objects ourselves - // as the view hierarchy is flattened - 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 accessibilityElementWithContainer:self node:node]; - // As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the - // coordinate system of the node with shouldRasterizeDescendants enabled - CGRect frame = [selfNode convertRect:node.bounds fromNode:node]; - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); - [_accessibleElements addObject:accessibilityElement]; - } - }); + if (viewNode.shouldRasterizeDescendants) { + _accessibleElements = ASCollectUIAccessibilityElementsForNode(viewNode, viewNode, self); 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) { + NSMutableArray *accessibleElements = [NSMutableArray array]; + + for (ASDisplayNode *subnode in viewNode.subnodes) { if (subnode.isAccessibilityElement) { + // An accessiblityElement can either be a UIView or a UIAccessibilityElement id accessiblityElement = nil; if (subnode.isLayerBacked) { - // The same comment for layer backed nodes is true as for subnodes within a shouldRasterizeDescendants node. - // See details above + // No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]; } else { accessiblityElement = subnode.view; } [accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self)]; - [_accessibleElements addObject:accessiblityElement]; - } else if ([subnode accessibilityElementCount] > 0) { // Check if it's an UIAccessibilityContainer - [_accessibleElements addObject:subnode.view]; + [accessibleElements addObject:accessiblityElement]; + } else if (subnode.isLayerBacked) { + // Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement + [accessibleElements addObjectsFromArray:ASCollectUIAccessibilityElementsForNode(viewNode, subnode, self)]; + } else if ([subnode accessibilityElementCount] > 0) { + // Add UIAccessibilityContainer + [accessibleElements addObject:subnode.view]; } } + _accessibleElements = [accessibleElements copy]; return _accessibleElements; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index abb2fedba0..eaf0cc9f59 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -721,7 +721,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo // Helper function with following logic: // - If the node is not loaded yet use the property from the pending state // - In case the node is loaded -// - Check if the node has a view and get the +// - Check if the node has a view and get the value from the view if loaded or from the pending state // - If view is not available, e.g. the node is layer backed return the property value #define _getAccessibilityFromViewOrProperty(nodeProperty, viewAndPendingViewStateProperty) __loaded(self) ? \ (_view ? _view.viewAndPendingViewStateProperty : nodeProperty )\