diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index bbe66ded72..2bb2a4c932 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -143,7 +143,14 @@ typedef struct { * this hook could be called up to 1 + (pJPEGcount * pJPEGrenderCount) times. The render count depends on how many times the downloader calls the * progressImage block. */ -- (void)hierarchyDisplayDidFinish; +AS_CATEGORY_IMPLEMENTABLE +- (void)hierarchyDisplayDidFinish NS_REQUIRES_SUPER; + +/** + * Only called on the root during yoga layout. + */ +AS_CATEGORY_IMPLEMENTABLE +- (void)willCalculateLayout:(ASSizeRange)constrainedSize NS_REQUIRES_SUPER; /** * Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly, diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index 8a2d7b3f49..2b80f2b5c3 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -69,8 +69,22 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion This is the best time to add gesture recognizers to the view. */ +AS_CATEGORY_IMPLEMENTABLE - (void)didLoad ASDISPLAYNODE_REQUIRES_SUPER; +/** + * An empty method that you can implement in a category to add global + * node initialization behavior. This method will be called by [ASDisplayNode init]. + */ +AS_CATEGORY_IMPLEMENTABLE +- (void)baseDidInit; + +/** + * An empty method that you can implement in a category to add global + * node deallocation behavior. This method will be called by [ASDisplayNode dealloc]. + */ +AS_CATEGORY_IMPLEMENTABLE +- (void)baseWillDealloc; #pragma mark - Layout /** @name Layout */ @@ -88,6 +102,7 @@ NS_ASSUME_NONNULL_BEGIN * @discussion Gives a chance for subclasses to perform actions after the subclass and superclass have finished laying * out. */ +AS_CATEGORY_IMPLEMENTABLE - (void)layoutDidFinish ASDISPLAYNODE_REQUIRES_SUPER; /** @@ -96,6 +111,7 @@ NS_ASSUME_NONNULL_BEGIN * @discussion When the .calculatedLayout property is set to a new ASLayout (directly from -calculateLayoutThatFits: or * calculated via use of -layoutSpecThatFits:), subclasses may inspect it here. */ +AS_CATEGORY_IMPLEMENTABLE - (void)calculatedLayoutDidChange ASDISPLAYNODE_REQUIRES_SUPER; @@ -162,15 +178,25 @@ NS_ASSUME_NONNULL_BEGIN * For descriptions, see definition. */ +AS_CATEGORY_IMPLEMENTABLE - (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; + +AS_CATEGORY_IMPLEMENTABLE - (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; +AS_CATEGORY_IMPLEMENTABLE - (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER; + +AS_CATEGORY_IMPLEMENTABLE - (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER; +AS_CATEGORY_IMPLEMENTABLE - (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER; + +AS_CATEGORY_IMPLEMENTABLE - (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER; +AS_CATEGORY_IMPLEMENTABLE - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; @@ -179,6 +205,7 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion Subclasses can override this method to react to a trait collection change. */ +AS_CATEGORY_IMPLEMENTABLE - (void)asyncTraitCollectionDidChange; #pragma mark - Drawing diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index dffe67bc32..27cedde8c7 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -338,6 +338,7 @@ return; } + [self willCalculateLayout:rootConstrainedSize]; [self enumerateInterfaceStateDelegates:^(id _Nonnull delegate) { if ([delegate respondsToSelector:@selector(nodeWillCalculateLayout:)]) { [delegate nodeWillCalculateLayout:rootConstrainedSize]; diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 0649f2d4b8..d3414b8949 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -94,7 +94,15 @@ AS_EXTERN NSInteger const ASDefaultDrawingPriority; * */ -@interface ASDisplayNode : NSObject +@interface ASDisplayNode : NSObject { +@public + /** + * The _context ivar is unused by Texture, but provided to enable advanced clients to make powerful extensions to base class functionality. + * For example, _context can be used to implement category methods on ASDisplayNode that add functionality to all node subclass types. + * Code demonstrating this technique can be found in the CatDealsCollectionView example. + */ + void *_context; +} /** @name Initializing a node object */ diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 7c93140fef..2ca35422e7 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -17,6 +17,7 @@ #import #import +#include #import #import @@ -104,6 +105,10 @@ _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) return result; } +void StubImplementationWithNoArgs(id receiver) {} +void StubImplementationWithSizeRange(id receiver, ASSizeRange sr) {} +void StubImplementationWithTwoInterfaceStates(id receiver, ASInterfaceState s0, ASInterfaceState s1) {} + /** * Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL. * @@ -247,6 +252,34 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) }); class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); + + // Add stub implementations for global methods that the client didn't + // implement in a category. We do this instead of hard-coding empty + // implementations to avoid a linker warning when it merges categories. + // Note: addMethod will not do anything if a method already exists. + if (self == ASDisplayNode.class) { + IMP noArgsImp = (IMP)StubImplementationWithNoArgs; + class_addMethod(self, @selector(baseDidInit), noArgsImp, "v@:"); + class_addMethod(self, @selector(baseWillDealloc), noArgsImp, "v@:"); + class_addMethod(self, @selector(didLoad), noArgsImp, "v@:"); + class_addMethod(self, @selector(layoutDidFinish), noArgsImp, "v@:"); + class_addMethod(self, @selector(didEnterPreloadState), noArgsImp, "v@:"); + class_addMethod(self, @selector(didExitPreloadState), noArgsImp, "v@:"); + class_addMethod(self, @selector(didEnterDisplayState), noArgsImp, "v@:"); + class_addMethod(self, @selector(didExitDisplayState), noArgsImp, "v@:"); + class_addMethod(self, @selector(didEnterVisibleState), noArgsImp, "v@:"); + class_addMethod(self, @selector(didExitVisibleState), noArgsImp, "v@:"); + class_addMethod(self, @selector(hierarchyDisplayDidFinish), noArgsImp, "v@:"); + class_addMethod(self, @selector(asyncTraitCollectionDidChange), noArgsImp, "v@:"); + class_addMethod(self, @selector(calculatedLayoutDidChange), noArgsImp, "v@:"); + + auto type0 = "v@:" + std::string(@encode(ASSizeRange)); + class_addMethod(self, @selector(willCalculateLayout:), (IMP)StubImplementationWithSizeRange, type0.c_str()); + + auto interfaceStateType = std::string(@encode(ASInterfaceState)); + auto type1 = "v@:" + interfaceStateType + interfaceStateType; + class_addMethod(self, @selector(interfaceStateDidChange:fromState:), (IMP)StubImplementationWithTwoInterfaceStates, type1.c_str()); + } } #if !AS_INITIALIZE_FRAMEWORK_MANUALLY @@ -309,6 +342,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _automaticallyRelayoutOnSafeAreaChanges = NO; _automaticallyRelayoutOnLayoutMarginsChanges = NO; + [self baseDidInit]; ASDisplayNodeLogEvent(self, @"init"); } @@ -433,6 +467,7 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); - (void)dealloc { _flags.isDeallocating = YES; + [self baseWillDealloc]; // Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes. ASDisplayNodeAssert(checkFlag(Synchronous) || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating. Node: %@", self); @@ -573,13 +608,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); }]; } -- (void)didLoad -{ - ASDisplayNodeAssertMainThread(); - - // Subclass hook -} - - (BOOL)isNodeLoaded { if (ASDisplayNodeThreadIsMain()) { @@ -1058,19 +1086,19 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); ASPerformBlockOnMainThread(^{ [self layout]; [self _layoutClipCornersIfNeeded]; - [self layoutDidFinish]; + [self _layoutDidFinish]; }); } [self _fallbackUpdateSafeAreaOnChildren]; } -- (void)layoutDidFinish +- (void)_layoutDidFinish { - // Hook for subclasses ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); ASDisplayNodeAssertTrue(self.isNodeLoaded); + [self layoutDidFinish]; } #pragma mark Calculation @@ -1172,11 +1200,6 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__); [self _pendingLayoutTransitionDidComplete]; } -- (void)calculatedLayoutDidChange -{ - // Subclass override -} - #pragma mark - Display NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; @@ -1394,11 +1417,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS } } -- (void)hierarchyDisplayDidFinish -{ - // Subclass hook -} - // Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. // For details look at the comment on the canCallSetNeedsDisplayOfLayer flag - (BOOL)_canCallSetNeedsDisplayOfLayer @@ -3010,12 +3028,12 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { if (nowPreload != wasPreload) { if (nowPreload) { - [self didEnterPreloadState]; + [self _didEnterPreloadState]; } else { // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller. // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. if ([self supportsRangeManagedInterfaceState]) { - [self didExitPreloadState]; + [self _didExitPreloadState]; } } } @@ -3066,9 +3084,9 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { } if (nowDisplay) { - [self didEnterDisplayState]; + [self _didEnterDisplayState]; } else { - [self didExitDisplayState]; + [self _didExitDisplayState]; } } @@ -3079,9 +3097,9 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { if (nowVisible != wasVisible) { if (nowVisible) { - [self didEnterVisibleState]; + [self _didEnterVisibleState]; } else { - [self didExitVisibleState]; + [self _didExitVisibleState]; } } @@ -3093,7 +3111,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { } ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@", NSStringFromASInterfaceStateChange(oldState, newState)); - [self interfaceStateDidChange:newState fromState:oldState]; + [self _interfaceStateDidChange:newState fromState:oldState]; } - (void)prepareForCATransactionCommit @@ -3102,11 +3120,11 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { [self applyPendingInterfaceState:ASInterfaceStateNone]; } -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +- (void)_interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { - // Subclass hook ASAssertUnlocked(__instanceLock__); ASDisplayNodeAssertMainThread(); + [self interfaceStateDidChange:newState fromState:oldState]; [self enumerateInterfaceStateDelegates:^(id del) { [del interfaceStateDidChange:newState fromState:oldState]; }]; @@ -3149,9 +3167,8 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { return ASInterfaceStateIncludesVisible(_interfaceState); } -- (void)didEnterVisibleState +- (void)_didEnterVisibleState { - // subclass override ASDisplayNodeAssertMainThread(); #if ASDISPLAYNODE_ASSERTIONS_ENABLED @@ -3162,6 +3179,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { #endif ASAssertUnlocked(__instanceLock__); + [self didEnterVisibleState]; [self enumerateInterfaceStateDelegates:^(id del) { [del didEnterVisibleState]; }]; @@ -3171,11 +3189,11 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { #endif } -- (void)didExitVisibleState +- (void)_didExitVisibleState { - // subclass override ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); + [self didExitVisibleState]; [self enumerateInterfaceStateDelegates:^(id del) { [del didExitVisibleState]; }]; @@ -3187,21 +3205,21 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { return ASInterfaceStateIncludesDisplay(_interfaceState); } -- (void)didEnterDisplayState +- (void)_didEnterDisplayState { - // subclass override ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); + [self didEnterDisplayState]; [self enumerateInterfaceStateDelegates:^(id del) { [del didEnterDisplayState]; }]; } -- (void)didExitDisplayState +- (void)_didExitDisplayState { - // subclass override ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); + [self didExitDisplayState]; [self enumerateInterfaceStateDelegates:^(id del) { [del didExitDisplayState]; }]; @@ -3238,11 +3256,12 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { }); } -- (void)didEnterPreloadState +- (void)_didEnterPreloadState { ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); - + [self didEnterPreloadState]; + // If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any, // so that its subnodes are inserted/deleted and start preloading right away. // @@ -3260,10 +3279,11 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { }]; } -- (void)didExitPreloadState +- (void)_didExitPreloadState { ASDisplayNodeAssertMainThread(); ASAssertUnlocked(__instanceLock__); + [self didExitPreloadState]; [self enumerateInterfaceStateDelegates:^(id del) { [del didExitPreloadState]; }]; @@ -3681,12 +3701,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { } } -#pragma mark - Trait Collection Hooks - -- (void)asyncTraitCollectionDidChange -{ - // Subclass override -} @end #pragma mark - ASDisplayNode (Debugging) diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 36e9e557f6..ce0e891e6e 100644 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -24,6 +24,12 @@ #define AS_BUILD_UIUSERINTERFACESTYLE 0 #endif +/** + * Decorates methods that clients can implement in categories on our base class. These methods + * will be stubbed with an empty implementation if no implementation is provided. + */ +#define AS_CATEGORY_IMPLEMENTABLE + #ifdef __GNUC__ # define ASDISPLAYNODE_GNUC(major, minor) \ (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))) diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj index 29fb32d342..bc9a71bfb5 100644 --- a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; + CC142599219742CA009851B7 /* ASDisplayNode+CatDeals.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC142598219742CA009851B7 /* ASDisplayNode+CatDeals.mm */; }; FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */; }; /* End PBXBuildFile section */ @@ -45,6 +46,8 @@ AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + CC142598219742CA009851B7 /* ASDisplayNode+CatDeals.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+CatDeals.mm"; sourceTree = ""; }; + CC14259A219746C2009851B7 /* ASDisplayNode+CatDeals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+CatDeals.h"; sourceTree = ""; }; F1E539014E1F516F00A8F167 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F3A72D578C378357FF56486A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresentingViewController.h; sourceTree = ""; }; @@ -97,6 +100,8 @@ AC3C4A661A11F47200143C57 /* AppDelegate.m */, AC3C4A681A11F47200143C57 /* ViewController.h */, AC3C4A691A11F47200143C57 /* ViewController.m */, + CC14259A219746C2009851B7 /* ASDisplayNode+CatDeals.h */, + CC142598219742CA009851B7 /* ASDisplayNode+CatDeals.mm */, 7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */, 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */, FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */, @@ -261,6 +266,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CC142599219742CA009851B7 /* ASDisplayNode+CatDeals.mm in Sources */, 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */, 7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */, AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, diff --git a/examples/CatDealsCollectionView/Sample/ASDisplayNode+CatDeals.h b/examples/CatDealsCollectionView/Sample/ASDisplayNode+CatDeals.h new file mode 100644 index 0000000000..a851e58122 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ASDisplayNode+CatDeals.h @@ -0,0 +1,19 @@ +// +// ASDisplayNode+CatDeals.h +// Sample +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASDisplayNode (CatDeals) + +@property (nullable, copy) NSString *catsLoggingID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/CatDealsCollectionView/Sample/ASDisplayNode+CatDeals.mm b/examples/CatDealsCollectionView/Sample/ASDisplayNode+CatDeals.mm new file mode 100644 index 0000000000..76d9eb2773 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ASDisplayNode+CatDeals.mm @@ -0,0 +1,61 @@ +// +// ASDisplayNode+CatDeals.mm +// Texture +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +#import "ASDisplayNode+CatDeals.h" + +#import + +// A place to store info on any display node in this app. +struct CatDealsNodeContext { + NSString *loggingID = nil; +}; + +// Convenience to cast _context into our struct reference. +NS_INLINE CatDealsNodeContext &GetNodeContext(ASDisplayNode *node) { + return *static_cast(node->_context); +} + +@implementation ASDisplayNode (CatDeals) + +- (void)baseDidInit +{ + _context = new CatDealsNodeContext; +} + +- (void)baseWillDealloc +{ + delete &GetNodeContext(self); +} + +- (void)setCatsLoggingID:(NSString *)catsLoggingID +{ + NSString *copy = [catsLoggingID copy]; + ASLockScopeSelf(); + GetNodeContext(self).loggingID = copy; +} + +- (NSString *)catsLoggingID +{ + ASLockScopeSelf(); + return GetNodeContext(self).loggingID; +} + +- (void)didEnterVisibleState +{ + if (NSString *loggingID = self.catsLoggingID) { + NSLog(@"Visible: %@", loggingID); + } +} + +- (void)didExitVisibleState +{ + if (NSString *loggingID = self.catsLoggingID) { + NSLog(@"NotVisible: %@", loggingID); + } +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index f118c282a2..6cd05accdb 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -8,6 +8,8 @@ // #import "ItemNode.h" + +#import "ASDisplayNode+CatDeals.h" #import "ItemStyles.h" #import "PlaceholderNetworkImageNode.h" #import @@ -71,6 +73,7 @@ const CGFloat kSoldOutGBHeight = 50.0; { [super setNodeModel:nodeModel]; + self.catsLoggingID = [NSString stringWithFormat:@"CatDeal#%ld", (long)nodeModel.identifier]; [self updateLabels]; [self updateAccessibilityIdentifier]; }