diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 04a39019a2..e3262c77d3 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -301,7 +301,7 @@ struct ASImageNodeDrawParameters { imageModificationBlock = drawParameter.imageModificationBlock; } - BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); + BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds); CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds); ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext; diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index a019c04966..e8c4f32073 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -24,6 +24,7 @@ // We can take this lock when we're setting displaySuspended and in setNeedsDisplay, so to not deadlock, this is recursive ASDN::RecursiveMutex _displaySuspendedLock; BOOL _displaySuspended; + BOOL _attemptedDisplayWhileZeroSized; struct { BOOL delegateDidChangeBounds:1; @@ -102,6 +103,11 @@ [super setBounds:bounds]; self.asyncdisplaykit_node.threadSafeBounds = bounds; } + + if (_attemptedDisplayWhileZeroSized && CGRectIsEmpty(bounds) == NO && self.needsDisplayOnBoundsChange == NO) { + _attemptedDisplayWhileZeroSized = NO; + [self setNeedsDisplay]; + } } #if DEBUG // These override is strictly to help detect application-level threading errors. Avoid method overhead in release. @@ -189,9 +195,9 @@ - (void)display { + ASDisplayNodeAssertMainThread(); [self _hackResetNeedsDisplay]; - ASDisplayNodeAssertMainThread(); if (self.isDisplaySuspended) { return; } @@ -201,7 +207,11 @@ - (void)display:(BOOL)asynchronously { - id<_ASDisplayLayerDelegate> __attribute__((objc_precise_lifetime)) strongAsyncDelegate; + if (CGRectIsEmpty(self.bounds)) { + _attemptedDisplayWhileZeroSized = YES; + } + + id<_ASDisplayLayerDelegate> NS_VALID_UNTIL_END_OF_SCOPE strongAsyncDelegate; { _asyncDelegateLock.lock(); strongAsyncDelegate = _asyncDelegate; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 9be86b49de..60162c9b77 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -19,6 +19,7 @@ #import "ASDisplayNodeTestsHelper.h" #import "UIView+ASConvenience.h" #import "ASCellNode.h" +#import "ASImageNode.h" // Conveniences for making nodes named a certain way #define DeclareNodeNamed(n) ASDisplayNode *n = [[[ASDisplayNode alloc] init] autorelease]; n.name = @#n @@ -1991,4 +1992,30 @@ static bool stringContainsPointer(NSString *description, const void *p) { XCTAssert([node loadStateChangedToNO]); } + +- (void)testThatNodeGetsRenderedIfItGoesFromZeroSizeToRealSizeButOnlyOnce +{ + NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"logo-square" + ofType:@"png" inDirectory:@"TestResources"]; + UIImage *image = [UIImage imageWithContentsOfFile:path]; + ASImageNode *node = [[[ASImageNode alloc] init] autorelease]; + node.image = image; + + // When rendered at zero-size, we get no contents + XCTAssert(CGSizeEqualToSize(node.bounds.size, CGSizeZero)); + [node recursivelyEnsureDisplaySynchronously:YES]; + XCTAssertNil(node.contents); + + // When size becomes positive, we got some new contents + node.bounds = CGRectMake(0, 0, 100, 100); + [node recursivelyEnsureDisplaySynchronously:YES]; + id contentsAfterRedisplay = node.contents; + XCTAssertNotNil(contentsAfterRedisplay); + + // When size changes again, we do not get new contents + node.bounds = CGRectMake(0, 0, 1000, 1000); + [node recursivelyEnsureDisplaySynchronously:YES]; + XCTAssertEqual(contentsAfterRedisplay, node.contents); +} + @end