diff --git a/CHANGELOG.md b/CHANGELOG.md index ab351bffe..40c83558f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) +- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 95e927667..3a537ca4b 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3079,6 +3079,20 @@ - (void)didEnterPreloadState { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + if (self.automaticallyManagesSubnodes) { + // Tell the node to apply its applicable pending layout, if any, so that its subnodes are inserted/deleted + // and start preloading right away. + // + // If this node has an up-to-date layout (and subnodes), calling layoutIfNeeded will be fast. + // + // If this node doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur + // (see __layout and _u_measureNodeWithBoundsIfNecessary:). + // This scenario should be uncommon, and running a measurement pass here is a fine trade-off because preloading + // any time after this point would be late. + [self layoutIfNeeded]; + } + [_interfaceStateDelegate didEnterPreloadState]; } diff --git a/Tests/ASDisplayNodeImplicitHierarchyTests.m b/Tests/ASDisplayNodeImplicitHierarchyTests.m index a3a7e160f..11e2ce4af 100644 --- a/Tests/ASDisplayNodeImplicitHierarchyTests.m +++ b/Tests/ASDisplayNodeImplicitHierarchyTests.m @@ -2,8 +2,6 @@ // ASDisplayNodeImplicitHierarchyTests.m // Texture // -// Created by Levi McCallum on 2/1/16. -// // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // This source code is licensed under the BSD-style license found in the // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional @@ -20,6 +18,7 @@ #import #import +#import #import "ASDisplayNodeTestsHelper.h" @interface ASSpecTestDisplayNode : ASDisplayNode @@ -101,6 +100,47 @@ - (void)testInitialNodeInsertionWithOrdering XCTAssertEqual(node.subnodes[4], node5); } +- (void)testInitialNodeInsertionWhenEnterPreloadState +{ + static CGSize kSize = {100, 100}; + + static NSInteger subnodeCount = 5; + NSMutableArray *subnodes = [NSMutableArray arrayWithCapacity:subnodeCount]; + for (NSInteger i = 0; i < subnodeCount; i++) { + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + // As we will involve a stack spec we have to give the nodes an intrinsic content size + subnode.style.preferredSize = kSize; + [subnodes addObject:subnode]; + } + + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; + node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { + ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]]; + + ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init]; + [stack1 setChildren:@[subnodes[0], subnodes[1]]]; + + ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init]; + [stack2 setChildren:@[subnodes[2], absoluteLayout]]; + + return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, subnodes[4]]]; + }; + + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); + [node recursivelySetInterfaceState:ASInterfaceStatePreload]; + + // No premature view allocation + XCTAssertFalse(node.isNodeLoaded); + // Subnodes should be inserted, laid out and entered preload state + XCTAssertTrue([subnodes isEqualToArray:node.subnodes]); + for (NSInteger i = 0; i < subnodeCount; i++) { + ASDisplayNode *subnode = subnodes[i]; + XCTAssertTrue(CGSizeEqualToSize(kSize, subnode.bounds.size)); + XCTAssertTrue(ASInterfaceStateIncludesPreload(subnode.interfaceState)); + } +} + - (void)testCalculatedLayoutHierarchyTransitions { static CGSize kSize = {100, 100};