mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-06 12:43:58 +00:00
* Rolling back CI to known version for now * Fix availability in tests * Commenting out for now to see if it builds * Fix up the Swift framework test * Fix availability
292 lines
11 KiB
Plaintext
292 lines
11 KiB
Plaintext
//
|
|
// _ASDisplayViewAccessiblity.mm
|
|
// Texture
|
|
//
|
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
|
// grant of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
|
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// 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, strong) ASDisplayNode *node;
|
|
@property (nonatomic, strong) 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 (@available(iOS 11, *)) {
|
|
[accessibilityElement setValue:node.accessibilityAttributedLabel forKey:@"accessibilityAttributedLabel"];
|
|
[accessibilityElement setValue:node.accessibilityAttributedHint forKey:@"accessibilityAttributedHint"];
|
|
[accessibilityElement setValue:node.accessibilityAttributedValue forKey:@"accessibilityAttributedValue"];
|
|
}
|
|
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, strong) UIView *container;
|
|
@property (nonatomic, strong) ASDisplayNode *node;
|
|
@property (nonatomic, strong) 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 array];
|
|
NSMutableArray<ASAccessibilityCustomAction *> *actions = [NSMutableArray array];
|
|
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 (AS_AT_LEAST_IOS11) {
|
|
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 setValue:attributedLabel forKey:@"accessibilityAttributedLabel"];
|
|
} else {
|
|
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 *_accessibleElements;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _ASDisplayView (UIAccessibilityContainer)
|
|
|
|
#pragma mark - UIAccessibility
|
|
|
|
- (void)setAccessibleElements:(NSArray *)accessibleElements
|
|
{
|
|
_accessibleElements = nil;
|
|
}
|
|
|
|
- (NSArray *)accessibleElements
|
|
{
|
|
ASDisplayNode *viewNode = self.asyncdisplaykit_node;
|
|
if (viewNode == nil) {
|
|
return @[];
|
|
}
|
|
|
|
if (_accessibleElements != nil) {
|
|
return _accessibleElements;
|
|
}
|
|
|
|
NSMutableArray *accessibleElements = [NSMutableArray array];
|
|
CollectAccessibilityElementsForView(self, accessibleElements);
|
|
SortAccessibilityElements(accessibleElements);
|
|
_accessibleElements = accessibleElements;
|
|
|
|
return _accessibleElements;
|
|
}
|
|
|
|
- (NSInteger)accessibilityElementCount
|
|
{
|
|
return self.accessibleElements.count;
|
|
}
|
|
|
|
- (id)accessibilityElementAtIndex:(NSInteger)index
|
|
{
|
|
return self.accessibleElements[index];
|
|
}
|
|
|
|
- (NSInteger)indexOfAccessibilityElement:(id)element
|
|
{
|
|
return [self.accessibleElements indexOfObjectIdenticalTo:element];
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|