Swiftgram/Source/Details/_ASDisplayViewAccessiblity.mm
appleguy 465abb1ded [License] Simplify the Texture license to be pure Apache 2 (removing ASDK-Licenses). (#1077)
* [License] Simplify the Texture license to be pure Apache 2 (removing ASDK-Licenses)

With permission of the Facebook Open Source team, we are simplifying the Texture
license so that clients can rely on the Apache 2 terms that most of Texture is
already covered by. This means that code originally forked from AsyncDisplayKit
will be re-licensed from "BSD 3-clause + PATENTS v2" to Apache 2 without a
PATENTS file.

After getting confirmation that the updates to these core files look good, we'll
propagate this new license header to all files (in this same PR) and get sign-off
from all parties before landing.

* [License] Update all Texture source files to be pure Apache 2.

* Changelog entry for Apache 2 license update.

* Revert "[License] Update all Texture source files to be pure Apache 2."

This reverts commit ffa0fbbba9717d871dd16c4b07539f2f8208fc2b.

* [License] Update all Texture source files to be pure Apache 2, maintaining copyrights.

* [License] Update CONTRIBUTING, README, Podspec & Dangerfile.
2018-08-28 07:39:18 -07:00

275 lines
10 KiB
Plaintext

//
// _ASDisplayViewAccessiblity.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef ASDK_ACCESSIBILITY_DISABLE
#import <AsyncDisplayKit/_ASDisplayView.h>
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <queue>
NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() {
return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton;
}
#pragma mark - 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)
{
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
static SortAccessibilityElementsComparator comparator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
comparator = ^NSComparisonResult(id<ASAccessibilityElementPositioning> a, id<ASAccessibilityElementPositioning> b) {
CGPoint originA = a.accessibilityFrame.origin;
CGPoint originB = b.accessibilityFrame.origin;
if (originA.y == originB.y) {
if (originA.x == originB.x) {
return NSOrderedSame;
}
return (originA.x < originB.x) ? NSOrderedAscending : NSOrderedDescending;
}
return (originA.y < originB.y) ? NSOrderedAscending : NSOrderedDescending;
};
});
[elements sortUsingComparator:comparator];
}
@interface ASAccessibilityElement : UIAccessibilityElement<ASAccessibilityElementPositioning>
@property (nonatomic) ASDisplayNode *node;
@property (nonatomic) ASDisplayNode *containerNode;
+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode;
@end
@implementation ASAccessibilityElement
+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode
{
ASAccessibilityElement *accessibilityElement = [[ASAccessibilityElement alloc] initWithAccessibilityContainer:container];
accessibilityElement.node = node;
accessibilityElement.containerNode = containerNode;
accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier;
accessibilityElement.accessibilityLabel = node.accessibilityLabel;
accessibilityElement.accessibilityHint = node.accessibilityHint;
accessibilityElement.accessibilityValue = node.accessibilityValue;
accessibilityElement.accessibilityTraits = node.accessibilityTraits;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
accessibilityElement.accessibilityAttributedLabel = node.accessibilityAttributedLabel;
accessibilityElement.accessibilityAttributedHint = node.accessibilityAttributedHint;
accessibilityElement.accessibilityAttributedValue = node.accessibilityAttributedValue;
}
#endif
return accessibilityElement;
}
- (CGRect)accessibilityFrame
{
CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node];
accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.accessibilityContainer);
return accessibilityFrame;
}
@end
#pragma mark - _ASDisplayView / UIAccessibilityContainer
@interface ASAccessibilityCustomAction : UIAccessibilityCustomAction<ASAccessibilityElementPositioning>
@property (nonatomic) UIView *container;
@property (nonatomic) ASDisplayNode *node;
@property (nonatomic) 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)
{
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
ASDisplayNodePerformBlockOnEveryNodeBFS(node, ^(ASDisplayNode * _Nonnull currentNode) {
// For every subnode that is layer backed or it's supernode has subtree rasterization enabled
// we have to create a UIAccessibilityElement as no view for this node exists
if (currentNode != containerNode && currentNode.isAccessibilityElement) {
UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode];
[elements addObject:accessibilityElement];
}
});
}
static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, _ASDisplayView *view, NSMutableArray *elements) {
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:container containerNode:container];
NSMutableArray<ASAccessibilityElement *> *labeledNodes = [[NSMutableArray alloc] init];
NSMutableArray<ASAccessibilityCustomAction *> *actions = [[NSMutableArray alloc] init];
std::queue<ASDisplayNode *> queue;
queue.push(container);
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);
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
NSArray *attributedLabels = [labeledNodes valueForKey:@"accessibilityAttributedLabel"];
NSMutableAttributedString *attributedLabel = [NSMutableAttributedString new];
[attributedLabels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx != 0) {
[attributedLabel appendAttributedString:[[NSAttributedString alloc] initWithString:@", "]];
}
[attributedLabel appendAttributedString:(NSAttributedString *)obj];
}];
accessiblityElement.accessibilityAttributedLabel = attributedLabel;
} else
#endif
{
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) {
CollectUIAccessibilityElementsForNode(node, node, view, elements);
return;
}
for (ASDisplayNode *subnode in node.subnodes) {
if (subnode.isAccessibilityElement) {
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
if (subnode.isLayerBacked) {
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node];
[elements addObject:accessiblityElement];
} else {
// Accessiblity element is not layer backed just add the view as accessibility element
[elements addObject:subnode.view];
}
} else if (subnode.isLayerBacked) {
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
CollectUIAccessibilityElementsForNode(subnode, node, view, elements);
} else if ([subnode accessibilityElementCount] > 0) {
// UIView is itself a UIAccessibilityContainer just add it
[elements addObject:subnode.view];
}
}
}
@interface _ASDisplayView () {
NSArray *_accessibilityElements;
}
@end
@implementation _ASDisplayView (UIAccessibilityContainer)
#pragma mark - UIAccessibility
- (void)setAccessibilityElements:(NSArray *)accessibilityElements
{
ASDisplayNodeAssertMainThread();
_accessibilityElements = nil;
}
- (NSArray *)accessibilityElements
{
ASDisplayNodeAssertMainThread();
ASDisplayNode *viewNode = self.asyncdisplaykit_node;
if (viewNode == nil) {
return @[];
}
if (_accessibilityElements == nil) {
NSMutableArray *accessibilityElements = [[NSMutableArray alloc] init];
CollectAccessibilityElementsForView(self, accessibilityElements);
SortAccessibilityElements(accessibilityElements);
_accessibilityElements = accessibilityElements;
}
return _accessibilityElements;
}
@end
#endif