diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 1d77edc23e..c9bc71fbb1 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -264,6 +264,10 @@ 698548661CA9E025008A345F /* ASEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 698548621CA9E025008A345F /* ASEnvironment.m */; }; 698C8B611CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 698C8B621CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; + 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; }; + 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; + 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; }; 69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; 69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; }; 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; @@ -745,6 +749,8 @@ 698548611CA9E025008A345F /* ASEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironment.h; sourceTree = ""; }; 698548621CA9E025008A345F /* ASEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEnvironment.m; sourceTree = ""; }; 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutableExtensibility.h; path = AsyncDisplayKit/Layout/ASLayoutableExtensibility.h; sourceTree = ""; }; + 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = ""; }; + 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = ""; }; 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = ""; }; 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = ""; }; 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; @@ -1123,6 +1129,8 @@ 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, 058D09E5195D050800B7D73C /* _ASDisplayView.mm */, + 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */, + 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */, 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */, 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */, 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */, @@ -1510,6 +1518,7 @@ 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */, 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */, CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */, + 69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, 058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */, 058D0A81195D05F900B7D73C /* ASThread.h in Headers */, ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */, @@ -1553,6 +1562,7 @@ DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */, 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */, 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */, + 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, @@ -1940,6 +1950,7 @@ 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */, 257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */, E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */, + 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */, 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */, @@ -2051,6 +2062,7 @@ 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, E55D86331CA8A14000A0C26F /* ASLayoutable.mm in Sources */, + 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index a6700327a7..ebfd1dab21 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -8,18 +8,11 @@ #import "_ASDisplayView.h" -#import - #import "_ASCoreAnimationExtras.h" -#import "_ASAsyncTransactionContainer.h" -#import "ASAssert.h" -#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" -#import - @interface _ASDisplayView () @property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; @@ -378,130 +371,3 @@ } #endif @end - - -#pragma mark - Accessibility - -static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - -@implementation UIAccessibilityElement (_ASDisplayView) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. - - // Update UIAccessibilityElement properties from node - self.accessibilityIdentifier = node.accessibilityIdentifier; - self.accessibilityLabel = node.accessibilityLabel; - self.accessibilityHint = node.accessibilityHint; - self.accessibilityValue = node.accessibilityValue; - self.accessibilityTraits = node.accessibilityTraits; -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); -} - -@end - -@implementation _ASDisplayView (UIAccessibilityContainer) - -#pragma mark - UIAccessibility - -- (NSArray *)accessibleElements -{ - _accessibleElements = [[NSMutableArray alloc] init]; - - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - - // Handle rasterize case - if (selfNode.shouldRasterizeDescendants) { - // In this case we have to go through the whole subnodes tree in BFS fashion and create all - // accessibility elements ourselves as the view hierarchy is flattened - - // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. - std::queue queue; - queue.push(selfNode); - - while (!queue.empty()) { - ASDisplayNode *node = queue.front(); - queue.pop(); - - // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element - if (node != selfNode && node.isAccessibilityElement) { - UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; - accessibilityElement.asyncdisplaykit_node = node; - [_accessibleElements addObject:accessibilityElement]; - } - - // Add all subnodes to process in next step - for (int i = 0; i < node.subnodes.count; i++) - queue.push(node.subnodes[i]); - } - 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) { - // Check if this subnode is a UIAccessibilityContainer - if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { - // We are good and the view is an UIAccessibilityContainer so add it - [_accessibleElements addObject:subnode.view]; - } else if (subnode.isAccessibilityElement) { - // Create a accessiblity element from the subnode - UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; - accessibilityElement.asyncdisplaykit_node = subnode; - [_accessibleElements addObject:accessibilityElement]; - } - } - - return _accessibleElements; -} - -- (NSInteger)accessibilityElementCount -{ - return [self accessibleElements].count; -} - -- (id)accessibilityElementAtIndex:(NSInteger)index -{ - if (_accessibleElements == nil) { - return nil; - } - - UIAccessibilityElement *accessibilityElement = [_accessibleElements objectAtIndex:index]; - ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; - if (accessibilityElementNode == nil) { - return nil; - } - - // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen - // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: - // the frame can change - - // Handle if node is rasterized - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode.shouldRasterizeDescendants) { - // We need to convert the accessibilityElementNode frame into the coordinate system of the selfNode - CGRect frame = [selfNode convertRect:accessibilityElementNode.bounds fromNode:accessibilityElementNode]; - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); - return accessibilityElement; - } - - // Handle non rasterized case - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityElementNode.frame, self); - return accessibilityElement; -} - -- (NSInteger)indexOfAccessibilityElement:(id)element -{ - if (_accessibleElements == nil) { - return NSNotFound; - } - - return [_accessibleElements indexOfObject:element]; -} - -@end diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h new file mode 100644 index 0000000000..2a4eca699d --- /dev/null +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h @@ -0,0 +1,9 @@ +/* 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. + */ + +#import diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm new file mode 100644 index 0000000000..f311447d0e --- /dev/null +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -0,0 +1,153 @@ +/* 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. + */ + +#import "_ASDisplayViewAccessiblity.h" +#import "_ASDisplayView.h" +#import "ASDisplayNode+FrameworkPrivate.h" + +#import +#import + + +#pragma mark - UIAccessibilityElement + +static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; + +@implementation UIAccessibilityElement (_ASDisplayView) + +- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node +{ + objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. + + // Update UIAccessibilityElement properties from node + self.accessibilityIdentifier = node.accessibilityIdentifier; + self.accessibilityLabel = node.accessibilityLabel; + self.accessibilityHint = node.accessibilityHint; + self.accessibilityValue = node.accessibilityValue; + self.accessibilityTraits = node.accessibilityTraits; +} + +- (ASDisplayNode *)asyncdisplaykit_node +{ + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); +} + +@end + + +#pragma mark - _ASDisplayView + +@interface _ASDisplayView () { + NSMutableArray *_accessibleElements; +} + +@end + +@implementation _ASDisplayView (UIAccessibilityContainer) + +#pragma mark - UIAccessibility + +- (NSArray *)accessibleElements +{ + ASDisplayNode *selfNode = self.asyncdisplaykit_node; + if (selfNode == nil) { + return nil; + } + + _accessibleElements = [[NSMutableArray alloc] init]; + + // Handle rasterize case + if (selfNode.shouldRasterizeDescendants) { + // In this case we have to go through the whole subnodes tree in BFS fashion and create all + // accessibility elements ourselves as the view hierarchy is flattened + + // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. + std::queue queue; + queue.push(selfNode); + + while (!queue.empty()) { + ASDisplayNode *node = queue.front(); + queue.pop(); + + // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element + if (node != selfNode && node.isAccessibilityElement) { + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; + accessibilityElement.asyncdisplaykit_node = node; + [_accessibleElements addObject:accessibilityElement]; + } + + // Add all subnodes to process in next step + for (int i = 0; i < node.subnodes.count; i++) + queue.push(node.subnodes[i]); + } + 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) { + // Check if this subnode is a UIAccessibilityContainer + if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { + // We are good and the view is an UIAccessibilityContainer so add it + [_accessibleElements addObject:subnode.view]; + } else if (subnode.isAccessibilityElement) { + // Create a accessiblity element from the subnode + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; + accessibilityElement.asyncdisplaykit_node = subnode; + [_accessibleElements addObject:accessibilityElement]; + } + } + + return _accessibleElements; +} + +- (NSInteger)accessibilityElementCount +{ + return [self accessibleElements].count; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index +{ + if (_accessibleElements == nil) { + return nil; + } + + UIAccessibilityElement *accessibilityElement = [_accessibleElements objectAtIndex:index]; + ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; + if (accessibilityElementNode == nil) { + return nil; + } + + // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen + // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: + // the frame can change + + // Handle if node is rasterized + ASDisplayNode *selfNode = self.asyncdisplaykit_node; + if (selfNode.shouldRasterizeDescendants) { + // We need to convert the accessibilityElementNode frame into the coordinate system of the selfNode + CGRect frame = [selfNode convertRect:accessibilityElementNode.bounds fromNode:accessibilityElementNode]; + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); + return accessibilityElement; + } + + // Handle non rasterized case + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityElementNode.frame, self); + return accessibilityElement; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element +{ + if (_accessibleElements == nil) { + return NSNotFound; + } + + return [_accessibleElements indexOfObject:element]; +} + +@end