From 0bd18c852233d7b2b0b433e031aea1beb10da86b Mon Sep 17 00:00:00 2001 From: Eric Scheers Date: Thu, 7 Sep 2017 22:05:30 +0200 Subject: [PATCH] [ASDisplayNode] Notify rasterized subnodes that render pass has completed (#532) * Notify rasterized subsides that render pass has completed * Traverse entire subnode tree notifying all subnodes * Add entry in changelog * Retrieve rasterizesSubtree flag while holding instance lock * Balance display delegate calls for rasterized subnodes --- CHANGELOG.md | 1 + Source/Private/ASDisplayNode+AsyncDisplay.mm | 15 ++++++ Tests/ASDisplayNodeTests.mm | 49 ++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d6a3d332..f98a440dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) - [ASEditableTextNode] added -editableTextNodeShouldBeginEditing to ASEditableTextNodeDelegate to mirror the corresponding method from UITextViewDelegate. [Yan S.](https://github.com/yans) [#535](https://github.com/TextureGroup/Texture/pull/535) - [Breaking] Remove APIs that have been deprecated since 2.0 and/or for at least 6 months [Huy Nguyen](https://github.com/nguyenhuy) [#529](https://github.com/TextureGroup/Texture/pull/529) +- [ASDisplayNode] Ensure `-displayWillStartAsynchronously:` and `-displayDidFinish` are invoked on rasterized subnodes. [Eric Scheers](https://github.com/smeis) [#532](https://github.com/TextureGroup/Texture/pull/532) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 00bb0ca66c..999c2552dd 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -23,6 +23,8 @@ #import #import #import +#import + @interface ASDisplayNode () <_ASDisplayLayerDelegate> @end @@ -302,6 +304,7 @@ } CALayer *layer = _layer; + BOOL rasterizesSubtree = _flags.rasterizesSubtree; __instanceLock__.unlock(); @@ -347,11 +350,23 @@ layer.contents = (id)image.CGImage; } [self didDisplayAsyncLayer:self.asyncLayer]; + + if (rasterizesSubtree) { + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + [node didDisplayAsyncLayer:node.asyncLayer]; + }); + } } }; // Call willDisplay immediately in either case [self willDisplayAsyncLayer:self.asyncLayer asynchronously:asynchronously]; + + if (rasterizesSubtree) { + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + [node willDisplayAsyncLayer:node.asyncLayer asynchronously:asynchronously]; + }); + } if (asynchronously) { // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index f1fb33d1b4..fac3f50929 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -110,6 +110,10 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @property (nonatomic) BOOL hasPreloaded; @property (nonatomic) BOOL preloadStateChangedToYES; @property (nonatomic) BOOL preloadStateChangedToNO; + +@property (nonatomic, assign) NSUInteger displayWillStartCount; +@property (nonatomic, assign) NSUInteger didDisplayCount; + @end @interface ASTestResponderNode : ASTestDisplayNode @@ -154,6 +158,18 @@ for (ASDisplayNode *n in @[ nodes ]) {\ } } +- (void)displayDidFinish +{ + [super displayDidFinish]; + _didDisplayCount++; +} + +- (void)displayWillStartAsynchronously:(BOOL)asynchronously +{ + [super displayWillStartAsynchronously:asynchronously]; + _displayWillStartCount++; +} + @end @interface UIDisplayNodeTestView : UIView @@ -2018,6 +2034,39 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssertThrows([rasterizedSupernode addSubnode:subnode]); } +- (void)testThatSubnodesGetDisplayUpdatesIfRasterized +{ + ASTestDisplayNode *supernode = [[ASTestDisplayNode alloc] init]; + supernode.frame = CGRectMake(0.0, 0.0, 100.0, 100.0); + [supernode enableSubtreeRasterization]; + + ASTestDisplayNode *subnode = [[ASTestDisplayNode alloc] init]; + ASTestDisplayNode *subSubnode = [[ASTestDisplayNode alloc] init]; + + ASSetDebugNames(supernode, subnode); + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + [subnode addSubnode:subSubnode]; + [supernode addSubnode:subnode]; + [window addSubnode:supernode]; + [window makeKeyAndVisible]; + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subnode.didDisplayCount == 1); + })); + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subSubnode.didDisplayCount == 1); + })); + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subnode.displayWillStartCount == 1); + })); + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subSubnode.displayWillStartCount == 1); + })); +} + // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2011 - (void)testThatLayerBackedSubnodesAreMarkedInvisibleBeforeDeallocWhenSupernodesViewIsRemovedFromHierarchyWhileBeingRetained {