mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-11 15:10:37 +00:00
* Fixes issues with accessibility elements in scroll views There are two changes here: 1. Because we can't reliably update the screen positions of accessibility elements contained within a scroll view, we need to calculate them on demand, so we override the accessibilityFrame method to do that. 2. Throwing away our calculated accessibility elements on frame change seemed to cause the accessibility system to be confused. Combining these two fixes together results in success, yay! * Don't set the accessibilityFrame, getter is overridden.
181 lines
6.5 KiB
Plaintext
181 lines
6.5 KiB
Plaintext
//
|
|
// _ASDisplayViewAccessiblity.mm
|
|
// AsyncDisplayKit
|
|
//
|
|
// 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 root directory of this source tree. An additional grant
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
|
|
#ifndef ASDK_ACCESSIBILITY_DISABLE
|
|
|
|
#import "_ASDisplayView.h"
|
|
#import "ASDisplayNodeExtras.h"
|
|
#import "ASDisplayNode+FrameworkPrivate.h"
|
|
|
|
#pragma mark - UIAccessibilityElement
|
|
|
|
typedef NSComparisonResult (^SortAccessibilityElementsComparator)(UIAccessibilityElement *, UIAccessibilityElement *);
|
|
|
|
/// 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(UIAccessibilityElement *a, UIAccessibilityElement *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
|
|
|
|
@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;
|
|
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
|
|
|
|
/// 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 shouldRasterizeDescendants 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];
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 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;
|
|
|
|
// Handle rasterize case
|
|
if (node.shouldRasterizeDescendants) {
|
|
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
|