From 93809bd4e72c3147d0204779bdd3dab61c3dab6e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 2 Mar 2017 11:51:16 -0800 Subject: [PATCH] Add a Convenience Property to Get Owning View Controller (#3076) * Add a convenience property -closestViewController * Address comments --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 +++++ Source/ASDisplayNode+Convenience.h | 27 ++++++++++++++ Source/ASDisplayNode+Convenience.m | 39 +++++++++++++++++++++ Source/ASDisplayNodeExtras.h | 5 +++ Source/ASDisplayNodeExtras.mm | 16 ++++++--- Source/AsyncDisplayKit.h | 1 + Source/Private/ASResponderChainEnumerator.m | 3 ++ 7 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 Source/ASDisplayNode+Convenience.h create mode 100644 Source/ASDisplayNode+Convenience.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 669519e353..5b0a62a12b 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -307,6 +307,8 @@ CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */; }; CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */; }; CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */; }; + CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC051F1E1D7A286A006434CB /* ASCALayerTests.m */; }; CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */; }; CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */; }; @@ -697,6 +699,8 @@ CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTableTests.m; sourceTree = ""; }; CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+IGListKitMethods.h"; sourceTree = ""; }; CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+IGListKitMethods.m"; sourceTree = ""; }; + CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; + CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; CC051F1E1D7A286A006434CB /* ASCALayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCALayerTests.m; sourceTree = ""; }; CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASUICollectionViewTests.m; sourceTree = ""; }; CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; @@ -900,6 +904,8 @@ 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */, 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, + CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */, + CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */, @@ -1453,6 +1459,7 @@ 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */, + CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, @@ -1916,6 +1923,7 @@ 696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */, 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */, 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */, + CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, diff --git a/Source/ASDisplayNode+Convenience.h b/Source/ASDisplayNode+Convenience.h new file mode 100644 index 0000000000..b75947c0dd --- /dev/null +++ b/Source/ASDisplayNode+Convenience.h @@ -0,0 +1,27 @@ +// +// ASDisplayNode+Convenience.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class UIViewController; + +@interface ASDisplayNode (Convenience) + +/** + * @abstract Returns the view controller nearest to this node in the view hierarchy. + * + * @warning This property may only be accessed on the main thread. This property may + * be @c nil until the node's view is actually hosted in the view hierarchy. + */ +@property (nonatomic, nullable, readonly) __kindof UIViewController *closestViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASDisplayNode+Convenience.m b/Source/ASDisplayNode+Convenience.m new file mode 100644 index 0000000000..a85b734338 --- /dev/null +++ b/Source/ASDisplayNode+Convenience.m @@ -0,0 +1,39 @@ +// +// ASDisplayNode+Convenience.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/24/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASDisplayNode+Convenience.h" + +#import + +#import +#import + +@implementation ASDisplayNode (Convenience) + +- (__kindof UIViewController *)closestViewController +{ + ASDisplayNodeAssertMainThread(); + + // Careful not to trigger node loading here. + if (!self.nodeLoaded) { + return nil; + } + + // Get the closest view. + UIView *view = ASFindClosestViewOfLayer(self.layer); + // Travel up the responder chain to find a view controller. + for (UIResponder *responder in [view asdk_responderChainEnumerator]) { + UIViewController *vc = ASDynamicCast(responder, UIViewController); + if (vc != nil) { + return vc; + } + } + return nil; +} + +@end diff --git a/Source/ASDisplayNodeExtras.h b/Source/ASDisplayNodeExtras.h index 82affa2ec5..c8e6c9483a 100644 --- a/Source/ASDisplayNodeExtras.h +++ b/Source/ASDisplayNodeExtras.h @@ -130,6 +130,11 @@ extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass */ extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT; +/** + * Given a layer, find the closest view it lives in, if any. + */ +extern UIView * _Nullable ASFindClosestViewOfLayer(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/Source/ASDisplayNodeExtras.mm b/Source/ASDisplayNodeExtras.mm index f5912f9790..094179c024 100644 --- a/Source/ASDisplayNodeExtras.mm +++ b/Source/ASDisplayNodeExtras.mm @@ -250,14 +250,20 @@ static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possible } extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) +{ + UIView *view = ASFindClosestViewOfLayer(layer); + if (UIWindow *window = ASDynamicCast(view, UIWindow)) { + return window; + } else { + return view.window; + } +} + +extern UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer) { while (layer != nil) { if (UIView *view = ASDynamicCast(layer.delegate, UIView)) { - if ([view isKindOfClass:[UIWindow class]]) { - return (UIWindow *)view; - } else { - return view.window; - } + return view; } layer = layer.superlayer; } diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 45c406c869..5e84654d17 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -9,6 +9,7 @@ // #import +#import #import #import diff --git a/Source/Private/ASResponderChainEnumerator.m b/Source/Private/ASResponderChainEnumerator.m index 2310c5c861..709443dc3d 100644 --- a/Source/Private/ASResponderChainEnumerator.m +++ b/Source/Private/ASResponderChainEnumerator.m @@ -7,6 +7,7 @@ // #import "ASResponderChainEnumerator.h" +#import @implementation ASResponderChainEnumerator { UIResponder *_currentResponder; @@ -14,6 +15,7 @@ - (instancetype)initWithResponder:(UIResponder *)responder { + ASDisplayNodeAssertMainThread(); if (self = [super init]) { _currentResponder = responder; } @@ -24,6 +26,7 @@ - (id)nextObject { + ASDisplayNodeAssertMainThread(); id result = [_currentResponder nextResponder]; _currentResponder = result; return result;