mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-20 21:29:00 +00:00
[Layout] Automatic measure pass in layout pass if not happened before (#2163)
* Automatic measure pass in layout pass if not happened before If no measure pass happened or the bounds changed between layout passes we manually trigger a measurement pass for the node using a size range equal to whatever bounds were provided to the node * Add test method that ensures that on a second layout pass with same bounds, layoutSpecThatFits: / layoutSpecBlock is not called
This commit is contained in:
parent
ea4d88e053
commit
027358fc6b
@ -1321,22 +1321,21 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
|
|
||||||
- (void)measureNodeWithBoundsIfNecessary:(CGRect)bounds
|
- (void)measureNodeWithBoundsIfNecessary:(CGRect)bounds
|
||||||
{
|
{
|
||||||
BOOL supportsRangedManagedInterfaceState = NO;
|
|
||||||
BOOL hasDirtyLayout = NO;
|
BOOL hasDirtyLayout = NO;
|
||||||
BOOL hasSupernode = NO;
|
CGSize calculatedLayoutSize = CGSizeZero;
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
supportsRangedManagedInterfaceState = [self supportsRangeManagedInterfaceState];
|
|
||||||
hasDirtyLayout = [self _hasDirtyLayout];
|
hasDirtyLayout = [self _hasDirtyLayout];
|
||||||
hasSupernode = (self.supernode != nil);
|
calculatedLayoutSize = _calculatedLayout.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally measure will be called before layout occurs. If this doesn't happen, nothing is going to call it at all.
|
// If no measure pass happened or the bounds changed between layout passes we manually trigger a measurement pass
|
||||||
// We simply call measureWithSizeRange: using a size range equal to whatever bounds were provided to that element
|
// for the node using a size range equal to whatever bounds were provided to the node
|
||||||
if (!hasSupernode && !supportsRangedManagedInterfaceState && hasDirtyLayout) {
|
if (hasDirtyLayout || CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) {
|
||||||
if (CGRectEqualToRect(bounds, CGRectZero)) {
|
if (CGRectEqualToRect(bounds, CGRectZero)) {
|
||||||
LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self);
|
LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self);
|
||||||
} else {
|
} else {
|
||||||
|
// This is a no op if the bounds size is the same as the cosntrained size we used to create the layout previously
|
||||||
[self measureWithSizeRange:ASSizeRangeMake(bounds.size, bounds.size)];
|
[self measureWithSizeRange:ASSizeRangeMake(bounds.size, bounds.size)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,25 +13,59 @@
|
|||||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||||
|
|
||||||
|
|
||||||
@interface ASDisplayNodeLayoutTests : XCTestCase
|
@interface ASDisplayNodeLayoutTests : XCTestCase
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation ASDisplayNodeLayoutTests
|
@implementation ASDisplayNodeLayoutTests
|
||||||
|
|
||||||
- (void)testMeasurePassOnLayoutIfNotHappenedBefore
|
- (void)testMeasureOnLayoutIfNotHappenedBefore
|
||||||
{
|
{
|
||||||
|
CGSize nodeSize = CGSizeMake(100, 100);
|
||||||
|
|
||||||
ASStaticSizeDisplayNode *displayNode = [ASStaticSizeDisplayNode new];
|
ASStaticSizeDisplayNode *displayNode = [ASStaticSizeDisplayNode new];
|
||||||
displayNode.staticSize = CGSizeMake(100, 100);
|
displayNode.staticSize = nodeSize;
|
||||||
displayNode.frame = CGRectMake(0, 0, 100, 100);
|
|
||||||
|
// Use a button node in here as ASButtonNode uses layoutSpecThatFits:
|
||||||
|
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||||
|
[displayNode addSubnode:buttonNode];
|
||||||
|
|
||||||
|
displayNode.frame = {.size = nodeSize};
|
||||||
|
buttonNode.frame = {.size = nodeSize};
|
||||||
|
|
||||||
ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||||
|
ASXCTAssertEqualSizes(buttonNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||||
|
|
||||||
// Trigger view creation and layout pass without a manual measure: call before so the automatic measurement
|
// Trigger view creation and layout pass without a manual measure: call before so the automatic measurement
|
||||||
// pass will trigger in the layout pass
|
// pass will trigger in the layout pass
|
||||||
[displayNode.view layoutIfNeeded];
|
[displayNode.view layoutIfNeeded];
|
||||||
|
|
||||||
ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeMake(100, 100), @"Automatic measurement pass should be happened in layout");
|
ASXCTAssertEqualSizes(displayNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||||
|
ASXCTAssertEqualSizes(buttonNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testMeasureOnLayoutIfNotHappenedBeforeForRangeManagedNodes
|
||||||
|
{
|
||||||
|
CGSize nodeSize = CGSizeMake(100, 100);
|
||||||
|
|
||||||
|
ASStaticSizeDisplayNode *displayNode = [ASStaticSizeDisplayNode new];
|
||||||
|
displayNode.staticSize = nodeSize;
|
||||||
|
|
||||||
|
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||||
|
[displayNode addSubnode:buttonNode];
|
||||||
|
|
||||||
|
[displayNode enterHierarchyState:ASHierarchyStateRangeManaged];
|
||||||
|
|
||||||
|
displayNode.frame = {.size = nodeSize};
|
||||||
|
buttonNode.frame = {.size = nodeSize};
|
||||||
|
|
||||||
|
ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||||
|
ASXCTAssertEqualSizes(buttonNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||||
|
|
||||||
|
// Trigger layout pass without a maeasurment pass before
|
||||||
|
[displayNode.view layoutIfNeeded];
|
||||||
|
|
||||||
|
ASXCTAssertEqualSizes(displayNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||||
|
ASXCTAssertEqualSizes(buttonNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@ -65,4 +99,30 @@
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
- (void)testMeasureOnLayoutIfNotHappenedBeforeNoRemeasureForSameBounds
|
||||||
|
{
|
||||||
|
CGSize nodeSize = CGSizeMake(100, 100);
|
||||||
|
|
||||||
|
ASStaticSizeDisplayNode *displayNode = [ASStaticSizeDisplayNode new];
|
||||||
|
displayNode.staticSize = nodeSize;
|
||||||
|
|
||||||
|
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||||
|
[displayNode addSubnode:buttonNode];
|
||||||
|
|
||||||
|
__block size_t numberOfLayoutSpecThatFitsCalls = 0;
|
||||||
|
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||||
|
__sync_fetch_and_add(&numberOfLayoutSpecThatFitsCalls, 1);
|
||||||
|
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:buttonNode];
|
||||||
|
};
|
||||||
|
|
||||||
|
displayNode.frame = {.size = nodeSize};
|
||||||
|
|
||||||
|
// Trigger initial layout pass without a measurement pass before
|
||||||
|
[displayNode.view layoutIfNeeded];
|
||||||
|
XCTAssertEqual(numberOfLayoutSpecThatFitsCalls, 1, @"Should measure during layout if not measured");
|
||||||
|
|
||||||
|
[displayNode measureWithSizeRange:ASSizeRangeMake(nodeSize, nodeSize)];
|
||||||
|
XCTAssertEqual(numberOfLayoutSpecThatFitsCalls, 1, @"Should not remeasure with same bounds");
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user