diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b8998d47b5..ea186a72c5 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1805,6 +1805,21 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + /** + * When passed node=nil, all methods in this family use the UIView-style + * behavior – that is, convert from/to window coordinates if there's a window, + * otherwise return the point untransformed. + */ + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertPoint:point fromLayer:window.layer]; + } else { + return point; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); @@ -1820,6 +1835,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertPoint:point toLayer:window.layer]; + } else { + return point; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); @@ -1835,6 +1860,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertRect:rect fromLayer:window.layer]; + } else { + return rect; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); @@ -1850,6 +1885,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertRect:rect toLayer:window.layer]; + } else { + return rect; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index 9993fa91cb..82affa2ec5 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -125,6 +125,11 @@ extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * */ extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT; +/** + * Given a layer, find the window it lives in, if any. + */ +extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT; + /** * Given two nodes, finds their most immediate common parent. Used for geometry conversion methods. * NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 45d64bcd7e..aa0588c646 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -249,6 +249,21 @@ static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possible return NO; } +extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) +{ + while (layer != nil) { + if (UIView *view = ASDynamicCast(layer.delegate, UIView)) { + if ([view isKindOfClass:[UIWindow class]]) { + return (UIWindow *)view; + } else { + return view.window; + } + } + layer = layer.superlayer; + } + return nil; +} + extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) { ASDisplayNode *possibleAncestor = node1; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 27f0d42d87..689da37e4e 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -2278,4 +2278,25 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssertLessThan(underlayIndex, overlayIndex); } +- (void)testThatConvertPointGoesToWindowWhenPassedNil +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.frame = CGRectMake(10, 10, 10, 10); + [window addSubnode:node]; + CGPoint expectedOrigin = CGPointMake(10, 10); + ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); +} + +- (void)testThatConvertPointGoesToWindowWhenPassedNil_layerBacked +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.layerBacked = YES; + node.frame = CGRectMake(10, 10, 10, 10); + [window addSubnode:node]; + CGPoint expectedOrigin = CGPointMake(10, 10); + ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); +} + @end diff --git a/Base/ASBaseDefines.h b/Base/ASBaseDefines.h index 88cc188130..8332a1da8c 100755 --- a/Base/ASBaseDefines.h +++ b/Base/ASBaseDefines.h @@ -184,4 +184,7 @@ #define ASOVERLOADABLE __attribute__((overloadable)) /// Ensure that class is of certain kind -#define ASDynamicCast(x, c) ((c *) ([x isKindOfClass:[c class]] ? x : nil)) +#define ASDynamicCast(x, c) ({ \ + id __val = x;\ + ((c *) ([__val isKindOfClass:[c class]] ? __val : nil));\ +})