Skip to content

Commit

Permalink
[ASDisplayNode] Implement accessibilityElementsHidden (#1859)
Browse files Browse the repository at this point in the history
Most of this code comes from an old PR that @fruitcoder put up #795 2 years ago.

When creating our array of accessibilityElements, we need to respect the value of `accessibilityElementsHidden`. If the value of this property changes, we need to invalidate the cached accessibility elements (unless we are in the experiment that doesn’t cache `accessibilityElements`).

I created a simple test app and made sure this matched UIKit’s implementation. I also added a test case that changes the value of `accessibilityElementsHidden` and makes sure the proper accessibilityElements are returned.
  • Loading branch information
rcancro committed Jun 3, 2020
1 parent 8b9683d commit e16fdd6
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 1 deletion.
12 changes: 11 additions & 1 deletion Source/Details/_ASDisplayViewAccessiblity.mm
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ + (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)containe
accessibilityElement.accessibilityHint = node.accessibilityHint;
accessibilityElement.accessibilityValue = node.accessibilityValue;
accessibilityElement.accessibilityTraits = node.accessibilityTraits;
accessibilityElement.accessibilityElementsHidden = node.accessibilityElementsHidden;
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
accessibilityElement.accessibilityAttributedLabel = node.accessibilityAttributedLabel;
accessibilityElement.accessibilityAttributedHint = node.accessibilityAttributedHint;
Expand Down Expand Up @@ -221,6 +222,11 @@ static BOOL recusivelyCheckSuperviewsForScrollView(UIView *view) {
return recusivelyCheckSuperviewsForScrollView(view.superview);
}

/// returns YES if this node should be considered "hidden" from the screen reader.
static BOOL nodeIsHiddenFromAcessibility(ASDisplayNode *node) {
return node.isHidden || node.alpha == 0.0 || node.accessibilityElementsHidden;
}

/// Collect all accessibliity elements for a given view and view node
static void CollectAccessibilityElements(ASDisplayNode *node, NSMutableArray *elements)
{
Expand Down Expand Up @@ -254,6 +260,10 @@ static void CollectAccessibilityElements(ASDisplayNode *node, NSMutableArray *el
return;
}

if (nodeIsHiddenFromAcessibility(node)) {
return;
}

// see if one of the subnodes is modal. If it is, then we only need to collect accessibilityElements from that
// node. If more than one subnode is modal, UIKit uses the last view in subviews as the modal view (it appears to
// be based on the index in the subviews array, not the location on screen). Let's do the same.
Expand All @@ -270,7 +280,7 @@ static void CollectAccessibilityElements(ASDisplayNode *node, NSMutableArray *el

for (ASDisplayNode *subnode in subnodes) {
// If a node is hidden or has an alpha of 0.0 we should not include it
if (subnode.hidden || subnode.alpha == 0.0) {
if (nodeIsHiddenFromAcessibility(subnode)) {
continue;
}

Expand Down
6 changes: 6 additions & 0 deletions Source/Private/ASDisplayNode+UIViewBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,13 @@ - (BOOL)accessibilityElementsHidden
- (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden
{
_bridge_prologue_write;
BOOL oldHiddenValue = _getFromViewOnly(accessibilityElementsHidden);
_setAccessibilityToViewAndProperty(_flags.accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden);

// if we made a change, we need to clear the view's accessibilityElements cache.
if (!ASActivateExperimentalFeature(ASExperimentalDoNotCacheAccessibilityElements) && self.isNodeLoaded && oldHiddenValue != accessibilityElementsHidden) {
[self invalidateAccessibilityElements];
}
}

- (BOOL)accessibilityViewIsModal
Expand Down
37 changes: 37 additions & 0 deletions Tests/ASDisplayViewAccessibilityTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -591,5 +591,42 @@ - (void)testMultipleSubnodesAreModal {
XCTAssertTrue([elements containsObject:modalNode2.view]);
}

- (void)testAccessibilityElementsHidden {

UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 568)];
ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;

ASViewController *vc = [[ASViewController alloc] initWithNode:node];
window.rootViewController = vc;
[window makeKeyAndVisible];
[window layoutIfNeeded];

ASTextNode *label1 = [[ASTextNode alloc] init];
label1.attributedText = [[NSAttributedString alloc] initWithString:@"on screen"];
label1.frame = CGRectMake(0, 0, 100, 20);

ASTextNode *label2 = [[ASTextNode alloc] init];
label2.attributedText = [[NSAttributedString alloc] initWithString:@"partially on screen y"];
label2.frame = CGRectMake(0, 20, 100, 20);

[node addSubnode:label1];
[node addSubnode:label2];

NSArray *elements = [node.view accessibilityElements];
XCTAssertTrue(elements.count == 2);
XCTAssertTrue([elements containsObject:label1.view]);
XCTAssertTrue([elements containsObject:label2.view]);

node.accessibilityElementsHidden = YES;
elements = [node.view accessibilityElements];
XCTAssertTrue(elements.count == 0);

node.accessibilityElementsHidden = NO;
elements = [node.view accessibilityElements];
XCTAssertTrue(elements.count == 2);
XCTAssertTrue([elements containsObject:label1.view]);
XCTAssertTrue([elements containsObject:label2.view]);
}

@end

0 comments on commit e16fdd6

Please sign in to comment.