From 5226879291a5ab0877722bed587bc2a8678d0d4a Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 9 Nov 2016 21:40:13 -0800 Subject: [PATCH 01/39] Some commit --- AsyncDisplayKit/ASDisplayNode.mm | 26 +++++++++++-------- AsyncDisplayKit/ASTableView.mm | 12 +++++---- .../ASTextNodeSnapshotTests.m | 6 ++--- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 231d7e7acb..ef05ff6a04 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1392,18 +1392,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); + /*__instanceLock__.lock(); if (_calculatedDisplayNodeLayout->layout == nil) { // Can't proceed without a layout as no constrained size would be available. If not layout exists at this moment // no measurement pass did happen just bail out for now __instanceLock__.unlock(); return; - } + }*/ [self invalidateCalculatedLayout]; - if (_supernode) { + /*if (_supernode) { ASDisplayNode *supernode = _supernode; __instanceLock__.unlock(); // Cause supernode's layout to be invalidated @@ -1413,9 +1413,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. - [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; + [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize];*/ - CGRect oldBounds = self.bounds; + /*CGRect oldBounds = self.bounds; CGSize oldSize = oldBounds.size; CGSize newSize = _calculatedDisplayNodeLayout->layout.size; @@ -1429,9 +1429,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - } + }*/ - __instanceLock__.unlock(); + //__instanceLock__.unlock(); } - (void)__setNeedsDisplay @@ -1495,11 +1495,15 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // 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 - if (supportsRangeManagedInterfaceState == NO && (hasDirtyLayout || CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO)) { + if (hasDirtyLayout) { 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); } else { - [self layoutThatFits:ASSizeRangeMake(bounds.size)]; + if (CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { + [self layoutThatFits:ASSizeRangeMake(bounds.size)]; + } else { + [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; + } } } } @@ -3039,9 +3043,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { ASDisplayNodeAssertMainThread(); - if (_calculatedDisplayNodeLayout->isDirty()) { + /*if (_calculatedDisplayNodeLayout->isDirty()) { return; - } + }*/ [self __layoutSublayouts]; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 10c4853f34..7cb7de99ce 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1517,15 +1517,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; - ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; +// ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; // Table view cells should always fill its content view width. // Normally the content view width equals to the constrained size width (which equals to the table view width). // If there is a mismatch between these values, for example after the table view entered or left editing mode, // content view width is preferred and used to re-measure the cell node. - if (contentViewWidth != constrainedSize.max.width) { - constrainedSize.min.width = contentViewWidth; - constrainedSize.max.width = contentViewWidth; + //if (contentViewWidth != constrainedSize.max.width) { + //constrainedSize.min.width = contentViewWidth; + //constrainedSize.max.width = contentViewWidth; // Re-measurement is done on main to ensure thread affinity. In the worst case, this is as fast as UIKit's implementation. // @@ -1534,6 +1534,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Also, in many cases, some nodes may not need to be re-measured at all, such as when user enters and then immediately leaves editing mode. // To avoid premature optimization and making such assumption, as well as to keep ASTableView simple, re-measurement is strictly done on main. CGSize oldSize = node.bounds.size; + ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeMake(contentViewWidth, 0.0), + CGSizeMake(contentViewWidth, CGFLOAT_MAX)); const CGSize calculatedSize = [node layoutThatFits:constrainedSize].size; node.frame = { .size = calculatedSize }; @@ -1542,7 +1544,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self beginUpdates]; [self endUpdates]; } - } + //} } #pragma mark - ASCellNodeDelegate diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index 1dac0c198c..811a967465 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -25,8 +25,8 @@ ASTextNode *textNode = [[ASTextNode alloc] init]; textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); + [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; ASSnapshotVerifyNode(textNode, nil); } @@ -40,9 +40,9 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar judar judar judar judar judar" attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; + textNode.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10); [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))]; textNode.frame = CGRectMake(50, 50, textNode.calculatedSize.width, textNode.calculatedSize.height); - textNode.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10); [backgroundView addSubview:textNode.view]; backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); @@ -62,9 +62,9 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"yolo" attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; + textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5); [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; textNode.frame = CGRectMake(50, 50, textNode.calculatedSize.width, textNode.calculatedSize.height); - textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5); [backgroundView addSubview:textNode.view]; backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); From c61ba65ba38d19a6f13d627fe8e32cf4c25f0068 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 10 Nov 2016 16:42:58 -0800 Subject: [PATCH 02/39] Some commit --- AsyncDisplayKit/ASDisplayNode.h | 2 + AsyncDisplayKit/ASDisplayNode.mm | 19 +- AsyncDisplayKit/ASTextNode.mm | 15 +- .../Sample.xcodeproj/project.pbxproj | 1228 ++++++----------- .../ASViewController/Sample/DetailRootNode.m | 2 + .../Sample/DetailViewController.m | 55 + .../Sample/SampleSizingNode.h | 17 + .../Sample/SampleSizingNode.m | 86 ++ 8 files changed, 580 insertions(+), 844 deletions(-) create mode 100644 examples/ASViewController/Sample/SampleSizingNode.h create mode 100644 examples/ASViewController/Sample/SampleSizingNode.m diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index dddc908239..bbc1453a84 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -254,6 +254,8 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ +- (CGSize)sizeThatFits:(CGSize)size; + /** * @abstract Asks the node to return a layout based on given size range. * diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ef05ff6a04..f1cb9daacf 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -703,6 +703,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Layout +- (CGSize)sizeThatFits:(CGSize)size +{ + return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, size)].size; +} + - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize { #pragma clang diagnostic push @@ -1495,15 +1500,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // 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 - if (hasDirtyLayout) { - 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); - } else { - if (CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { - [self layoutThatFits:ASSizeRangeMake(bounds.size)]; - } else { - [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; - } + 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); + } else { + if (CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { + [self layoutThatFits:ASSizeRangeMake(bounds.size)]; } } } diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 25ff6cd86a..741a3db4f8 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -237,6 +237,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return [self _rendererWithBounds:self.threadSafeBounds]; } +- (ASTextKitRenderer *)_rendererWithBoundsSlow:(CGRect)bounds +{ + ASDN::MutexLocker l(__instanceLock__); + + CGSize constrainedSize = bounds.size; + constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); + constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + return [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] + constrainedSize:constrainedSize]; +} + - (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds { ASDN::MutexLocker l(__instanceLock__); @@ -404,7 +415,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self setNeedsDisplay]; - CGSize size = [self _renderer].size; + CGSize size = [self _rendererWithBoundsSlow:{.size=constrainedSize}].size; if (_attributedText.length > 0) { self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; @@ -537,7 +548,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top); - ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; + ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:drawParameterBounds]; // Fill background if (backgroundColor != nil) { diff --git a/examples/ASViewController/Sample.xcodeproj/project.pbxproj b/examples/ASViewController/Sample.xcodeproj/project.pbxproj index eeef9f4c40..1d11b3492e 100644 --- a/examples/ASViewController/Sample.xcodeproj/project.pbxproj +++ b/examples/ASViewController/Sample.xcodeproj/project.pbxproj @@ -1,833 +1,395 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 06EE2E0ABEB6289D4775A867 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Copy Pods Resources - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" - - showEnvVarsInLog - 0 - - 0DFDB4376BA084DAC7C1976E - - children - - 15AD337503831C4D33FF8B3A - 97482F27BE2F7583EFE1BC2C - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 15AD337503831C4D33FF8B3A - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.debug.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig - sourceTree - <group> - - 23FC03B282CBD9014D868DF6 - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Embed Pods Frameworks - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" - - showEnvVarsInLog - 0 - - 465082D55CCF1B0CB1AEBACC - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - path - libPods-Sample.a - sourceTree - BUILT_PRODUCTS_DIR - - 478C8D7C412DCBDFE14640D8 - - children - - 465082D55CCF1B0CB1AEBACC - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - 5CF3EF5E344946731D4F13F2 - - fileRef - 465082D55CCF1B0CB1AEBACC - isa - PBXBuildFile - - 694993C41C8B334F00491CA5 - - children - - 694993CF1C8B334F00491CA5 - 694993CE1C8B334F00491CA5 - 0DFDB4376BA084DAC7C1976E - 478C8D7C412DCBDFE14640D8 - - isa - PBXGroup - sourceTree - <group> - - 694993C51C8B334F00491CA5 - - attributes - - LastUpgradeCheck - 0720 - ORGANIZATIONNAME - AsyncDisplayKit - TargetAttributes - - 694993CC1C8B334F00491CA5 - - CreatedOnToolsVersion - 7.2.1 - - - - buildConfigurationList - 694993C81C8B334F00491CA5 - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - Base - - mainGroup - 694993C41C8B334F00491CA5 - productRefGroup - 694993CE1C8B334F00491CA5 - projectDirPath - - projectReferences - - projectRoot - - targets - - 694993CC1C8B334F00491CA5 - - - 694993C81C8B334F00491CA5 - - buildConfigurations - - 694993E21C8B334F00491CA5 - 694993E31C8B334F00491CA5 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 694993C91C8B334F00491CA5 - - buildActionMask - 2147483647 - files - - 694993D81C8B334F00491CA5 - 694993D51C8B334F00491CA5 - 694993D21C8B334F00491CA5 - 69DCA5221C8B3D30006FF548 - 69DCA5281C8BE031006FF548 - 69DCA5251C8BE01F006FF548 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 694993CA1C8B334F00491CA5 - - buildActionMask - 2147483647 - files - - 5CF3EF5E344946731D4F13F2 - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 694993CB1C8B334F00491CA5 - - buildActionMask - 2147483647 - files - - 694993E01C8B334F00491CA5 - 694993DD1C8B334F00491CA5 - - isa - PBXResourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 694993CC1C8B334F00491CA5 - - buildConfigurationList - 694993E41C8B334F00491CA5 - buildPhases - - 80035273449C25F4B2E1454F - 694993C91C8B334F00491CA5 - 694993CA1C8B334F00491CA5 - 694993CB1C8B334F00491CA5 - 06EE2E0ABEB6289D4775A867 - 23FC03B282CBD9014D868DF6 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Sample - productName - Sample - productReference - 694993CD1C8B334F00491CA5 - productType - com.apple.product-type.application - - 694993CD1C8B334F00491CA5 - - explicitFileType - wrapper.application - includeInIndex - 0 - isa - PBXFileReference - path - Sample.app - sourceTree - BUILT_PRODUCTS_DIR - - 694993CE1C8B334F00491CA5 - - children - - 694993CD1C8B334F00491CA5 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 694993CF1C8B334F00491CA5 - - children - - 694993D31C8B334F00491CA5 - 694993D41C8B334F00491CA5 - 694993D61C8B334F00491CA5 - 694993D71C8B334F00491CA5 - 69DCA5201C8B3D30006FF548 - 69DCA5211C8B3D30006FF548 - 69DCA5231C8BE01F006FF548 - 69DCA5241C8BE01F006FF548 - 69DCA5261C8BE031006FF548 - 69DCA5271C8BE031006FF548 - 694993DC1C8B334F00491CA5 - 694993D01C8B334F00491CA5 - - isa - PBXGroup - path - Sample - sourceTree - <group> - - 694993D01C8B334F00491CA5 - - children - - 694993E11C8B334F00491CA5 - 694993DE1C8B334F00491CA5 - 694993D11C8B334F00491CA5 - - isa - PBXGroup - name - Supporting Files - sourceTree - <group> - - 694993D11C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - main.m - sourceTree - <group> - - 694993D21C8B334F00491CA5 - - fileRef - 694993D11C8B334F00491CA5 - isa - PBXBuildFile - - 694993D31C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - AppDelegate.h - sourceTree - <group> - - 694993D41C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - AppDelegate.m - sourceTree - <group> - - 694993D51C8B334F00491CA5 - - fileRef - 694993D41C8B334F00491CA5 - isa - PBXBuildFile - - 694993D61C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - ViewController.h - sourceTree - <group> - - 694993D71C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - ViewController.m - sourceTree - <group> - - 694993D81C8B334F00491CA5 - - fileRef - 694993D71C8B334F00491CA5 - isa - PBXBuildFile - - 694993DC1C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - folder.assetcatalog - path - Assets.xcassets - sourceTree - <group> - - 694993DD1C8B334F00491CA5 - - fileRef - 694993DC1C8B334F00491CA5 - isa - PBXBuildFile - - 694993DE1C8B334F00491CA5 - - children - - 694993DF1C8B334F00491CA5 - - isa - PBXVariantGroup - name - LaunchScreen.storyboard - sourceTree - <group> - - 694993DF1C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - file.storyboard - name - Base - path - Base.lproj/LaunchScreen.storyboard - sourceTree - <group> - - 694993E01C8B334F00491CA5 - - fileRef - 694993DE1C8B334F00491CA5 - isa - PBXBuildFile - - 694993E11C8B334F00491CA5 - - isa - PBXFileReference - lastKnownFileType - text.plist.xml - path - Info.plist - sourceTree - <group> - - 694993E21C8B334F00491CA5 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - DEBUG_INFORMATION_FORMAT - dwarf - ENABLE_STRICT_OBJC_MSGSEND - YES - ENABLE_TESTABILITY - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_NO_COMMON_BLOCKS - YES - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 7.0 - MTL_ENABLE_DEBUG_INFO - YES - ONLY_ACTIVE_ARCH - YES - SDKROOT - iphoneos - - isa - XCBuildConfiguration - name - Debug - - 694993E31C8B334F00491CA5 - - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - CLANG_CXX_LANGUAGE_STANDARD - gnu++0x - CLANG_CXX_LIBRARY - libc++ - CLANG_ENABLE_MODULES - YES - CLANG_ENABLE_OBJC_ARC - YES - CLANG_WARN_BOOL_CONVERSION - YES - CLANG_WARN_CONSTANT_CONVERSION - YES - CLANG_WARN_DIRECT_OBJC_ISA_USAGE - YES_ERROR - CLANG_WARN_EMPTY_BODY - YES - CLANG_WARN_ENUM_CONVERSION - YES - CLANG_WARN_INT_CONVERSION - YES - CLANG_WARN_OBJC_ROOT_CLASS - YES_ERROR - CLANG_WARN_UNREACHABLE_CODE - YES - CLANG_WARN__DUPLICATE_METHOD_MATCH - YES - CODE_SIGN_IDENTITY[sdk=iphoneos*] - iPhone Developer - COPY_PHASE_STRIP - NO - DEBUG_INFORMATION_FORMAT - dwarf-with-dsym - ENABLE_NS_ASSERTIONS - NO - ENABLE_STRICT_OBJC_MSGSEND - YES - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_NO_COMMON_BLOCKS - YES - GCC_WARN_64_TO_32_BIT_CONVERSION - YES - GCC_WARN_ABOUT_RETURN_TYPE - YES_ERROR - GCC_WARN_UNDECLARED_SELECTOR - YES - GCC_WARN_UNINITIALIZED_AUTOS - YES_AGGRESSIVE - GCC_WARN_UNUSED_FUNCTION - YES - GCC_WARN_UNUSED_VARIABLE - YES - IPHONEOS_DEPLOYMENT_TARGET - 7.0 - MTL_ENABLE_DEBUG_INFO - NO - SDKROOT - iphoneos - VALIDATE_PRODUCT - YES - - isa - XCBuildConfiguration - name - Release - - 694993E41C8B334F00491CA5 - - buildConfigurations - - 694993E51C8B334F00491CA5 - 694993E61C8B334F00491CA5 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 694993E51C8B334F00491CA5 - - baseConfigurationReference - 15AD337503831C4D33FF8B3A - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - org.AsyncDisplayKit.Sample - PRODUCT_NAME - $(TARGET_NAME) - - isa - XCBuildConfiguration - name - Debug - - 694993E61C8B334F00491CA5 - - baseConfigurationReference - 97482F27BE2F7583EFE1BC2C - buildSettings - - ASSETCATALOG_COMPILER_APPICON_NAME - AppIcon - INFOPLIST_FILE - Sample/Info.plist - LD_RUNPATH_SEARCH_PATHS - $(inherited) @executable_path/Frameworks - PRODUCT_BUNDLE_IDENTIFIER - org.AsyncDisplayKit.Sample - PRODUCT_NAME - $(TARGET_NAME) - - isa - XCBuildConfiguration - name - Release - - 69DCA5201C8B3D30006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - DetailViewController.h - sourceTree - <group> - - 69DCA5211C8B3D30006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - DetailViewController.m - sourceTree - <group> - - 69DCA5221C8B3D30006FF548 - - fileRef - 69DCA5211C8B3D30006FF548 - isa - PBXBuildFile - - 69DCA5231C8BE01F006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - DetailRootNode.h - sourceTree - <group> - - 69DCA5241C8BE01F006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - DetailRootNode.m - sourceTree - <group> - - 69DCA5251C8BE01F006FF548 - - fileRef - 69DCA5241C8BE01F006FF548 - isa - PBXBuildFile - - 69DCA5261C8BE031006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - path - DetailCellNode.h - sourceTree - <group> - - 69DCA5271C8BE031006FF548 - - fileEncoding - 4 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - path - DetailCellNode.m - sourceTree - <group> - - 69DCA5281C8BE031006FF548 - - fileRef - 69DCA5271C8BE031006FF548 - isa - PBXBuildFile - - 80035273449C25F4B2E1454F - - buildActionMask - 2147483647 - files - - inputPaths - - isa - PBXShellScriptBuildPhase - name - Check Pods Manifest.lock - outputPaths - - runOnlyForDeploymentPostprocessing - 0 - shellPath - /bin/sh - shellScript - diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null -if [[ $? != 0 ]] ; then - cat << EOM -error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. -EOM - exit 1 -fi - - showEnvVarsInLog - 0 - - 97482F27BE2F7583EFE1BC2C - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods-Sample.release.xcconfig - path - Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig - sourceTree - <group> - - - rootObject - 694993C51C8B334F00491CA5 - - +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */; }; + 694993D21C8B334F00491CA5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D11C8B334F00491CA5 /* main.m */; }; + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D41C8B334F00491CA5 /* AppDelegate.m */; }; + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; + 698247E91DD51C8D004D1E37 /* SampleSizingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 698247E81DD51C8D004D1E37 /* SampleSizingNode.m */; }; + 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5211C8B3D30006FF548 /* DetailViewController.m */; }; + 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */; }; + 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5271C8BE031006FF548 /* DetailCellNode.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 15AD337503831C4D33FF8B3A /* 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 = ""; }; + 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 694993D41C8B334F00491CA5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 694993D61C8B334F00491CA5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 694993D71C8B334F00491CA5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 698247E71DD51C8D004D1E37 /* SampleSizingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleSizingNode.h; sourceTree = ""; }; + 698247E81DD51C8D004D1E37 /* SampleSizingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SampleSizingNode.m; sourceTree = ""; }; + 69DCA5201C8B3D30006FF548 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; + 69DCA5211C8B3D30006FF548 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailRootNode.h; sourceTree = ""; }; + 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailRootNode.m; sourceTree = ""; }; + 69DCA5261C8BE031006FF548 /* DetailCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailCellNode.h; sourceTree = ""; }; + 69DCA5271C8BE031006FF548 /* DetailCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailCellNode.m; sourceTree = ""; }; + 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 694993CA1C8B334F00491CA5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0DFDB4376BA084DAC7C1976E /* Pods */ = { + isa = PBXGroup; + children = ( + 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */, + 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 478C8D7C412DCBDFE14640D8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 694993C41C8B334F00491CA5 = { + isa = PBXGroup; + children = ( + 694993CF1C8B334F00491CA5 /* Sample */, + 694993CE1C8B334F00491CA5 /* Products */, + 0DFDB4376BA084DAC7C1976E /* Pods */, + 478C8D7C412DCBDFE14640D8 /* Frameworks */, + ); + sourceTree = ""; + }; + 694993CE1C8B334F00491CA5 /* Products */ = { + isa = PBXGroup; + children = ( + 694993CD1C8B334F00491CA5 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 694993CF1C8B334F00491CA5 /* Sample */ = { + isa = PBXGroup; + children = ( + 694993D31C8B334F00491CA5 /* AppDelegate.h */, + 694993D41C8B334F00491CA5 /* AppDelegate.m */, + 694993D61C8B334F00491CA5 /* ViewController.h */, + 694993D71C8B334F00491CA5 /* ViewController.m */, + 69DCA5201C8B3D30006FF548 /* DetailViewController.h */, + 69DCA5211C8B3D30006FF548 /* DetailViewController.m */, + 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */, + 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */, + 69DCA5261C8BE031006FF548 /* DetailCellNode.h */, + 69DCA5271C8BE031006FF548 /* DetailCellNode.m */, + 694993DC1C8B334F00491CA5 /* Assets.xcassets */, + 694993D01C8B334F00491CA5 /* Supporting Files */, + 698247E71DD51C8D004D1E37 /* SampleSizingNode.h */, + 698247E81DD51C8D004D1E37 /* SampleSizingNode.m */, + ); + path = Sample; + sourceTree = ""; + }; + 694993D01C8B334F00491CA5 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 694993E11C8B334F00491CA5 /* Info.plist */, + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */, + 694993D11C8B334F00491CA5 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 694993CC1C8B334F00491CA5 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 80035273449C25F4B2E1454F /* [CP] Check Pods Manifest.lock */, + 694993C91C8B334F00491CA5 /* Sources */, + 694993CA1C8B334F00491CA5 /* Frameworks */, + 694993CB1C8B334F00491CA5 /* Resources */, + 06EE2E0ABEB6289D4775A867 /* [CP] Copy Pods Resources */, + 23FC03B282CBD9014D868DF6 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 694993CD1C8B334F00491CA5 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 694993C51C8B334F00491CA5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = AsyncDisplayKit; + TargetAttributes = { + 694993CC1C8B334F00491CA5 = { + CreatedOnToolsVersion = 7.2.1; + }; + }; + }; + buildConfigurationList = 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 694993C41C8B334F00491CA5; + productRefGroup = 694993CE1C8B334F00491CA5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 694993CC1C8B334F00491CA5 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 694993CB1C8B334F00491CA5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */, + 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06EE2E0ABEB6289D4775A867 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 23FC03B282CBD9014D868DF6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 80035273449C25F4B2E1454F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 694993C91C8B334F00491CA5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, + 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, + 698247E91DD51C8D004D1E37 /* SampleSizingNode.m in Sources */, + 694993D21C8B334F00491CA5 /* main.m in Sources */, + 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */, + 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */, + 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 694993DF1C8B334F00491CA5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 694993E21C8B334F00491CA5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 694993E31C8B334F00491CA5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 694993E51C8B334F00491CA5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 694993E61C8B334F00491CA5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E21C8B334F00491CA5 /* Debug */, + 694993E31C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 694993E51C8B334F00491CA5 /* Debug */, + 694993E61C8B334F00491CA5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 694993C51C8B334F00491CA5 /* Project object */; +} diff --git a/examples/ASViewController/Sample/DetailRootNode.m b/examples/ASViewController/Sample/DetailRootNode.m index 333af36e6c..11091cea05 100644 --- a/examples/ASViewController/Sample/DetailRootNode.m +++ b/examples/ASViewController/Sample/DetailRootNode.m @@ -28,6 +28,8 @@ static const NSInteger kImageHeight = 200; @property (nonatomic, copy) NSString *imageCategory; @property (nonatomic, strong) ASCollectionNode *collectionNode; +@property (nonatomic, strong) ASDisplayNode *backgroundNode; + @end diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index 6b6b1a902b..f9f243ce79 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -19,9 +19,36 @@ #import #import "DetailRootNode.h" +#import "SampleSizingNode.h" + +@interface DetailViewController () +@property (strong, nonatomic) SampleSizingNode *sizingNode; + +@end @implementation DetailViewController +#pragma mark - Lifecycle + +- (instancetype)initWithNode:(DetailRootNode *)node +{ + self = [super initWithNode:node]; + + // Set the sizing delegate of the root node to the container + self.sizingNode = [SampleSizingNode new]; + self.sizingNode.autoresizingMask = UIViewAutoresizingNone; + self.sizingNode.delegate = self; + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubnode:self.sizingNode]; +} + #pragma mark - Rotation - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator @@ -30,4 +57,32 @@ [self.node.collectionNode.view.collectionViewLayout invalidateLayout]; } +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + + [self updateNodeLayout]; +} + +#pragma mark - Update the node based on the new size + +- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode +{ + // ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers + [self updateNodeLayout]; +} + +- (void)updateNodeLayout +{ + // Adjust the layout on the new layout + + // Use the bounds of the view and get the fitting size + CGSize size = [self.sizingNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, 100.0)]; + size.width -= 10; + //[self.sizingNode setNeedsLayout]; + self.sizingNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, + (self.view.bounds.size.height - size.height) / 2.0, + size.width, size.height); +} + @end diff --git a/examples/ASViewController/Sample/SampleSizingNode.h b/examples/ASViewController/Sample/SampleSizingNode.h new file mode 100644 index 0000000000..6543006cf3 --- /dev/null +++ b/examples/ASViewController/Sample/SampleSizingNode.h @@ -0,0 +1,17 @@ +// +// SampleSizingNode.h +// Sample +// +// Created by Michael Schneider on 11/10/16. +// Copyright © 2016 AsyncDisplayKit. All rights reserved. +// + +#import + +@interface ASDisplayNodeSizingDelegate : NSObject +- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; +@end + +@interface SampleSizingNode : ASDisplayNode +@property (nonatomic, weak) id delegate; +@end diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m new file mode 100644 index 0000000000..703135be13 --- /dev/null +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -0,0 +1,86 @@ +// +// SampleSizingNode.m +// Sample +// +// Created by Michael Schneider on 11/10/16. +// Copyright © 2016 AsyncDisplayKit. All rights reserved. +// + +#import "SampleSizingNode.h" + +@interface SampleSizingNode () +@property (nonatomic, strong) ASDisplayNode *subnode; +@property (nonatomic, assign) NSInteger state; + +@property (nonatomic, strong) ASTextNode *textNode; +@end + +@implementation SampleSizingNode + +- (instancetype)init +{ + self = [super init]; + if (self) { + + self.automaticallyManagesSubnodes = YES; + + //_subnode = [ASDisplayNode new]; + //_subnode.backgroundColor = [UIColor redColor]; + + _textNode = [ASTextNode new]; + _textNode.backgroundColor = [UIColor blueColor]; + _textNode.autoresizingMask = UIViewAutoresizingNone; + + _state = 0; + } + return self; +} + +- (void)didLoad +{ + [super didLoad]; + + [self stateChanged]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.state = 1; + }); +} + +#pragma mark - State Management + +- (void)setState:(NSInteger)state +{ + _state = state; + [self stateChanged]; +} + +- (void)stateChanged +{ + NSString *text = self.state == 0 ? @"Bla Bla" : @"Bla Blaa sd fkj as;l dkf"; + self.textNode.attributedText = [[NSAttributedString alloc] initWithString:text]; + + // Invalidate the layout for now and bubble it up until the root node to let the size provider know that + // that a size change happened + [self setNeedsLayout]; + + // If someone calls `setNeedsLayout` we have to inform the sizing delegate of the root node to be able + // to let them now that a size change happened + if ([self.delegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { + [self.delegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; + } +} + + +#pragma mark - ASDisplayNode + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + // Layout description based on state + //self.subnode.style.preferredSize = constrainedSize.max; + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; +} + + +@end From 6f40e9a6540c0be49aaced4f14fb399c35a693aa Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 10 Nov 2016 21:10:03 -0800 Subject: [PATCH 03/39] Some more stuff in flux --- AsyncDisplayKit/ASDisplayNode.mm | 12 +++- .../Sample/DetailViewController.m | 59 ++++++++++++++++--- .../Sample/SampleSizingNode.h | 1 + .../Sample/SampleSizingNode.m | 27 ++++++--- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index f1cb9daacf..a008bd66bd 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -716,10 +716,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma clang diagnostic pop } +- (ASLayout *)cacheLayoutThatFits:(ASSizeRange)constrainedSize +{ + return [self cacheLayoutThatFits:constrainedSize parentSize:constrainedSize.max]; +} + - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { ASDN::MutexLocker l(__instanceLock__); + + return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; +} +- (ASLayout *)cacheLayoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize +{ if ([self shouldCalculateLayoutWithConstrainedSize:constrainedSize parentSize:parentSize] == NO) { ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; @@ -1504,7 +1514,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); } else { if (CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { - [self layoutThatFits:ASSizeRangeMake(bounds.size)]; + [self cacheLayoutThatFits:ASSizeRangeMake(bounds.size)]; } } } diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index f9f243ce79..685be7b108 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -49,12 +49,17 @@ [self.view addSubnode:self.sizingNode]; } -#pragma mark - Rotation - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +- (void)viewWillAppear:(BOOL)animated { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - [self.node.collectionNode.view.collectionViewLayout invalidateLayout]; + [super viewWillAppear:animated]; + + // Initial size of sizing node + self.sizingNode.frame = CGRectMake(100, 100, 50, 50); + + // Start some timer to chang ethe size randomly + [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { + //[self updateNodeLayoutRandom]; + }]; } - (void)viewDidLayoutSubviews @@ -66,23 +71,61 @@ #pragma mark - Update the node based on the new size +// The sizing delegate will get callbacks if the size did invalidate of the display node. It's the job of the delegate +// to get the new size from the display node and update the frame based on the returned size - (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode { - // ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers [self updateNodeLayout]; + + /*dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self updateNodeLayoutRandom]; + });*/ + + /*[NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { + [self updateNodeLayoutRandom]; + }];*/ } - (void)updateNodeLayout { // Adjust the layout on the new layout - + //return; // Use the bounds of the view and get the fitting size + // This does not have any side effects, but can be called on the main thread without any problems CGSize size = [self.sizingNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, 100.0)]; - size.width -= 10; + //size.width -= 10; //[self.sizingNode setNeedsLayout]; self.sizingNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, (self.view.bounds.size.height - size.height) / 2.0, size.width, size.height); + + //dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.sizingNode.frame = CGRectInset(self.sizingNode.frame, 10, 10); + //}); } +- (void)updateNodeLayoutRandom +{ + // Pick a randome width and height and set the frame of the node + CGSize size = CGSizeZero; + size.width = arc4random_uniform(self.view.bounds.size.width); + size.height = arc4random_uniform(self.view.bounds.size.height); + + //[self.sizingNode setNeedsLayout]; + self.sizingNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, + (self.view.bounds.size.height - size.height) / 2.0, + size.width, size.height); + +} + + +#pragma mark - Rotation + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + [self.node.collectionNode.view.collectionViewLayout invalidateLayout]; +} + + @end diff --git a/examples/ASViewController/Sample/SampleSizingNode.h b/examples/ASViewController/Sample/SampleSizingNode.h index 6543006cf3..d02b99b8db 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.h +++ b/examples/ASViewController/Sample/SampleSizingNode.h @@ -8,6 +8,7 @@ #import +// ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers @interface ASDisplayNodeSizingDelegate : NSObject - (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; @end diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m index 703135be13..e59a855758 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.m +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -24,12 +24,19 @@ self.automaticallyManagesSubnodes = YES; - //_subnode = [ASDisplayNode new]; - //_subnode.backgroundColor = [UIColor redColor]; + self.backgroundColor = [UIColor greenColor]; _textNode = [ASTextNode new]; _textNode.backgroundColor = [UIColor blueColor]; - _textNode.autoresizingMask = UIViewAutoresizingNone; + //_textNode.autoresizingMask = UIViewAutoresizingNone; + + _subnode = [ASDisplayNode new]; + _subnode.backgroundColor = [UIColor redColor]; + _subnode.automaticallyManagesSubnodes = YES; + _subnode.layoutSpecBlock = ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; + }; _state = 0; } @@ -61,7 +68,8 @@ self.textNode.attributedText = [[NSAttributedString alloc] initWithString:text]; // Invalidate the layout for now and bubble it up until the root node to let the size provider know that - // that a size change happened + // that a size change could have happened + // --> Do we even need to invalidate the layout? [self setNeedsLayout]; // If someone calls `setNeedsLayout` we have to inform the sizing delegate of the root node to be able @@ -77,9 +85,14 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { // Layout description based on state - //self.subnode.style.preferredSize = constrainedSize.max; - UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; + // UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); + // return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; + + //return [ASWrapperLayoutSpec wrapperWithLayoutElement:self.subnode]; + return [ASCenterLayoutSpec + centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY + sizingOptions:ASCenterLayoutSpecSizingOptionDefault + child:self.subnode]; } From bcb032d5a522403ff61971de065dd773ad6be611 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 11 Nov 2016 15:09:40 -0800 Subject: [PATCH 04/39] Make tests happy --- AsyncDisplayKit/ASTextNode.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 741a3db4f8..f4cacda904 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -242,8 +242,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASDN::MutexLocker l(__instanceLock__); CGSize constrainedSize = bounds.size; - constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); - constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + if (_constrainedSize.width == -INFINITY) { + constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); + constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + } return [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] constrainedSize:constrainedSize]; } From 0f8eac4757475ed6ecc301e0bbd7d8cb70114333 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 11 Nov 2016 15:51:14 -0800 Subject: [PATCH 05/39] Some other commit --- AsyncDisplayKit/ASButtonNode.mm | 5 + AsyncDisplayKit/ASCellNode.mm | 26 ++-- AsyncDisplayKit/ASDisplayNode.h | 18 +++ AsyncDisplayKit/ASDisplayNode.mm | 117 +++++++++--------- AsyncDisplayKit/ASTableView.mm | 14 +-- .../Details/UIView+ASConvenience.h | 1 + AsyncDisplayKit/Details/_ASDisplayLayer.mm | 6 + .../Private/ASDisplayNode+UIViewBridge.mm | 28 +++++ .../Private/ASDisplayNodeInternal.h | 2 + AsyncDisplayKit/Private/_ASPendingState.mm | 13 ++ examples/ASDKgram/Sample/CommentsNode.m | 2 +- examples/ASDKgram/Sample/PhotoCellNode.m | 4 +- .../Sample/DetailViewController.m | 52 +++++++- .../Sample/SampleSizingNode.h | 6 +- .../Sample/SampleSizingNode.m | 62 +++++++--- examples/SocialAppLayout/Sample/PostNode.m | 2 +- 16 files changed, 258 insertions(+), 100 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index fda284ad70..0520e66ac3 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -172,6 +172,7 @@ if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { _imageNode.image = newImage; [self setNeedsLayout]; + [self invalidateSize]; } } @@ -195,6 +196,7 @@ _titleNode.attributedText = newTitle; self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; + [self invalidateSize]; } } @@ -218,6 +220,7 @@ if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { _backgroundImageNode.image = newImage; [self setNeedsLayout]; + [self invalidateSize]; } } @@ -235,6 +238,7 @@ _contentSpacing = contentSpacing; [self setNeedsLayout]; + [self invalidateSize]; } - (BOOL)laysOutHorizontally @@ -251,6 +255,7 @@ _laysOutHorizontally = laysOutHorizontally; [self setNeedsLayout]; + [self invalidateSize]; } - (ASVerticalAlignment)contentVerticalAlignment diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index cad63a8230..77270c3736 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -29,7 +29,7 @@ #pragma mark - #pragma mark ASCellNode -@interface ASCellNode () +@interface ASCellNode () { ASDisplayNodeViewControllerBlock _viewControllerBlock; ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; @@ -58,6 +58,10 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; + + // ASCellNode acts as sizing container for the node + self.sizingDelegate = self; + return self; } @@ -117,15 +121,19 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init _viewControllerNode.frame = self.bounds; } -- (void)__setNeedsLayout +- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode { - CGSize oldSize = self.calculatedSize; - [super __setNeedsLayout]; - - //Adding this lock because lock used to be held when this method was called. Not sure if it's necessary for - //didRelayoutFromOldSize:toNewSize: - ASDN::MutexLocker l(__instanceLock__); - [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; + ASDN::MutexLocker l(__instanceLock__); + + CGSize oldSize = self.calculatedSize; + ASLayout *layout = [self layoutThatFits:self.constrainedSizeForCalculatedLayout]; + + // TODO: Needs proper adjustment + CGRect f = self.frame; + f.size = layout.size; + self.frame = f; + + [self didRelayoutFromOldSize:oldSize toNewSize:layout.size]; } - (void)transitionLayoutWithAnimation:(BOOL)animated diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index bbc1453a84..cb1594049a 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -26,6 +26,19 @@ NS_ASSUME_NONNULL_BEGIN @class ASDisplayNode; +// TODO: Extract to ASDisplayNode+Layout.h +// ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers +@protocol ASDisplayNodeSizingDelegate +@required +/** + Called after the display node state update happened (layout invlidation) that could lead to a + + The delegate can use this callback to appropriately resize the node frame to fit the new + node size. The node will not resize itself. + */ +- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; +@end + /** * UIView creation block. Used to create the backing view of a new display node. */ @@ -254,6 +267,9 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ +@property (nonatomic, readwrite, weak, nullable) id sizingDelegate; +- (void)invalidateSize; + - (CGSize)sizeThatFits:(CGSize)size; /** @@ -648,6 +664,8 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)setNeedsLayout; +- (void)layoutIfNeeded; + @property (nonatomic, strong, nullable) id contents; // default=nil @property (nonatomic, assign) BOOL clipsToBounds; // default==NO @property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index a008bd66bd..dbe83499b8 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -703,6 +703,33 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Layout +- (void)invalidateSize +{ + ASDisplayNodeAssertThreadAffinity(self); + + __instanceLock__.lock(); + + [self invalidateCalculatedLayout]; + + // This is the root node. Let the delegate know that the size changed + // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able + // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy + if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { + [self.sizingDelegate displayNodeDidInvalidateSize:self]; + } + + if (_supernode) { + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); + // Cause supernode's layout to be invalidated + // We need to release the lock to prevent a deadlock + [supernode invalidateSize]; + return; + } + + __instanceLock__.unlock(); +} + - (CGSize)sizeThatFits:(CGSize)size { return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, size)].size; @@ -716,20 +743,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma clang diagnostic pop } -- (ASLayout *)cacheLayoutThatFits:(ASSizeRange)constrainedSize -{ - return [self cacheLayoutThatFits:constrainedSize parentSize:constrainedSize.max]; -} - - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { ASDN::MutexLocker l(__instanceLock__); - return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; -} - -- (ASLayout *)cacheLayoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize -{ if ([self shouldCalculateLayoutWithConstrainedSize:constrainedSize parentSize:parentSize] == NO) { ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; @@ -1402,51 +1419,41 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self displayImmediately]; } -//Calling this with the lock held can lead to deadlocks. Always call *unlocked* - (void)__setNeedsLayout +{ + ASDN::MutexLocker l(__instanceLock__); + + // This will cause the next call to -layoutThatFits:parentSize: to compute a new layout instead of returning + // the cached layout in case the constrained or parent size did not change + _calculatedDisplayNodeLayout->invalidate(); +} + +/** + * Returns a BOOL indicating whether the layer has been marked as needing a layout update. + */ +- (BOOL)__needsLayout +{ + ASDN::MutexLocker l(__instanceLock__); + return _calculatedDisplayNodeLayout->isDirty(); +} + +/** + * The node's supernodes are traversed until a ancestor node is found that does not require layout. Then layout + * is performed on the entire node-tree beneath that ancestor + */ +- (void)__layoutIfNeeded { ASDisplayNodeAssertThreadAffinity(self); + __instanceLock__.lock(); + ASDisplayNode *supernode = _supernode; + __instanceLock__.unlock(); - /*__instanceLock__.lock(); - - if (_calculatedDisplayNodeLayout->layout == nil) { - // Can't proceed without a layout as no constrained size would be available. If not layout exists at this moment - // no measurement pass did happen just bail out for now - __instanceLock__.unlock(); - return; - }*/ - - [self invalidateCalculatedLayout]; - - /*if (_supernode) { - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - // Cause supernode's layout to be invalidated - // We need to release the lock to prevent a deadlock - [supernode setNeedsLayout]; - return; + if ([supernode __needsLayout]) { + [supernode __layoutIfNeeded]; + } else { + // Layout all subviews starting from the first node that needs layout + [self __layout]; } - - // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. - [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize];*/ - - /*CGRect oldBounds = self.bounds; - CGSize oldSize = oldBounds.size; - CGSize newSize = _calculatedDisplayNodeLayout->layout.size; - - if (! CGSizeEqualToSize(oldSize, newSize)) { - self.bounds = (CGRect){ oldBounds.origin, newSize }; - - // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint - // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - CGPoint anchorPoint = self.anchorPoint; - CGPoint oldPosition = self.position; - CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; - CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - }*/ - - //__instanceLock__.unlock(); } - (void)__setNeedsDisplay @@ -1512,10 +1519,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // for the node using a size range equal to whatever bounds were provided to the node 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); - } else { - if (CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { - [self cacheLayoutThatFits:ASSizeRangeMake(bounds.size)]; - } + } else if (hasDirtyLayout || CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { + [self layoutThatFits:ASSizeRangeMake(bounds.size)]; } } @@ -3054,9 +3059,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) { ASDisplayNodeAssertMainThread(); - /*if (_calculatedDisplayNodeLayout->isDirty()) { + if (_calculatedDisplayNodeLayout->isDirty()) { return; - }*/ + } [self __layoutSublayouts]; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 7cb7de99ce..ec1d8a3019 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1509,6 +1509,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - _ASTableViewCellDelegate +#pragma mark - _ASTableViewCellDelegate + - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell { ASCellNode *node = tableViewCell.node; @@ -1517,15 +1519,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; -// ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; + ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; // Table view cells should always fill its content view width. // Normally the content view width equals to the constrained size width (which equals to the table view width). // If there is a mismatch between these values, for example after the table view entered or left editing mode, // content view width is preferred and used to re-measure the cell node. - //if (contentViewWidth != constrainedSize.max.width) { - //constrainedSize.min.width = contentViewWidth; - //constrainedSize.max.width = contentViewWidth; + if (contentViewWidth != constrainedSize.max.width) { + constrainedSize.min.width = contentViewWidth; + constrainedSize.max.width = contentViewWidth; // Re-measurement is done on main to ensure thread affinity. In the worst case, this is as fast as UIKit's implementation. // @@ -1534,8 +1536,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Also, in many cases, some nodes may not need to be re-measured at all, such as when user enters and then immediately leaves editing mode. // To avoid premature optimization and making such assumption, as well as to keep ASTableView simple, re-measurement is strictly done on main. CGSize oldSize = node.bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeMake(contentViewWidth, 0.0), - CGSizeMake(contentViewWidth, CGFLOAT_MAX)); const CGSize calculatedSize = [node layoutThatFits:constrainedSize].size; node.frame = { .size = calculatedSize }; @@ -1544,7 +1544,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self beginUpdates]; [self endUpdates]; } - //} + } } #pragma mark - ASCellNodeDelegate diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/AsyncDisplayKit/Details/UIView+ASConvenience.h index 24a9021cfd..9c918689ec 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/AsyncDisplayKit/Details/UIView+ASConvenience.h @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setNeedsDisplay; - (void)setNeedsLayout; +- (void)layoutIfNeeded; @end diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index a1d74c15fd..dd7f70ee8f 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -122,6 +122,12 @@ ASDisplayNodeAssertMainThread(); [super setNeedsLayout]; } + +- (void)layoutIfNeeded +{ + ASDisplayNodeAssertMainThread(); + [super layoutIfNeeded]; +} #endif - (void)layoutSublayers diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index f4e67b7f9a..efa1c50d06 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -354,6 +354,34 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo } } +- (void)layoutIfNeeded +{ + _bridge_prologue_write; + BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); + if (shouldApply) { + // The node is loaded and we're on main. + // Quite the opposite of setNeedsDisplay, we must call __layoutIfNeeded before messaging + // the view or layer to ensure that measurement and implicitly added subnodes have been handled. + + // Calling __layoutIfNeeded while holding the property lock can cause deadlocks + _bridge_prologue_write_unlock; + [self __layoutIfNeeded]; + _bridge_prologue_write; + _messageToViewOrLayer(layoutIfNeeded); + } else if (__loaded(self)) { + // The node is loaded but we're not on main. + // We will call [self __setNeedsLayout] when we apply + // the pending state. We need to call it on main if the node is loaded + // to support automatic subnode management. + [ASDisplayNodeGetPendingState(self) layoutIfNeeded]; + } else { + // The node is not loaded and we're not on main. + _bridge_prologue_write_unlock; + [self __layoutIfNeeded]; + _bridge_prologue_write; + } +} + - (BOOL)isOpaque { _bridge_prologue_read; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 8aa400cb54..c26eb1c014 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -205,6 +205,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (void)__setNeedsLayout; +- (void)__layoutIfNeeded; + /** Invoked after a call to setNeedsDisplay to the underlying view */ diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index c01470e258..b9e1abcb46 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -24,6 +24,7 @@ typedef struct { // Properties int needsDisplay:1; int needsLayout:1; + int layoutIfNeeded:1; // Flags indicating that a given property should be applied to the view at creation int setClipsToBounds:1; @@ -272,6 +273,11 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; _flags.needsLayout = YES; } +- (void)layoutIfNeeded +{ + _flags.layoutIfNeeded = YES; +} + - (void)setClipsToBounds:(BOOL)flag { clipsToBounds = flag; @@ -764,6 +770,9 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [layer setNeedsLayout]; + if (flags.layoutIfNeeded) + [layer layoutIfNeeded]; + if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -891,6 +900,9 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [view setNeedsLayout]; + + if (flags.layoutIfNeeded) + [view layoutIfNeeded]; if (flags.setAsyncTransactionContainer) view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -1105,6 +1117,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; || flags.setEdgeAntialiasingMask || flags.needsDisplay || flags.needsLayout + || flags.layoutIfNeeded || flags.setAsyncTransactionContainer || flags.setOpaque || flags.setIsAccessibilityElement diff --git a/examples/ASDKgram/Sample/CommentsNode.m b/examples/ASDKgram/Sample/CommentsNode.m index 67859bd37f..29cea828e3 100644 --- a/examples/ASDKgram/Sample/CommentsNode.m +++ b/examples/ASDKgram/Sample/CommentsNode.m @@ -79,7 +79,7 @@ labelsIndex++; } - [self setNeedsLayout]; + [self invalidateSize]; } } diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index 176cf4b608..89bd91e5ac 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -81,7 +81,7 @@ // where as local variable will never change if (locationModel == _photoModel.location) { _photoLocationLabel.attributedText = [photo locationAttributedStringWithFontSize:FONT_SIZE]; - [self setNeedsLayout]; + [self invalidateSize]; } }]; @@ -224,7 +224,7 @@ if (photo.commentFeed.numberOfItemsInFeed > 0) { [_photoCommentsView updateWithCommentFeedModel:photo.commentFeed]; - [self setNeedsLayout]; + [self invalidateSize]; } } diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index 685be7b108..e246ae56ab 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -21,9 +21,12 @@ #import "DetailRootNode.h" #import "SampleSizingNode.h" -@interface DetailViewController () +@interface DetailViewController () @property (strong, nonatomic) SampleSizingNode *sizingNode; +@property (strong, nonatomic) ASNetworkImageNode *imageNode; +@property (strong, nonatomic) ASButtonNode *buttonNode; + @end @implementation DetailViewController @@ -35,9 +38,22 @@ self = [super initWithNode:node]; // Set the sizing delegate of the root node to the container - self.sizingNode = [SampleSizingNode new]; - self.sizingNode.autoresizingMask = UIViewAutoresizingNone; - self.sizingNode.delegate = self; + _sizingNode = [SampleSizingNode new]; + _sizingNode.autoresizingMask = UIViewAutoresizingNone; + _sizingNode.sizingDelegate = self; + + _imageNode = [ASNetworkImageNode new]; + _imageNode.needsDisplayOnBoundsChange = YES; + _imageNode.backgroundColor = [UIColor brownColor]; + _imageNode.style.preferredSize = CGSizeMake(100, 100); + _imageNode.URL = [NSURL URLWithString:@"http://www.classicwings-bavaria.com/bavarian-pictures/chitty-chitty-bang-bang-castle.jpg"]; + + _buttonNode = [ASButtonNode new]; + _buttonNode.backgroundColor = [UIColor yellowColor]; + [_buttonNode setTitle:@"Some Title" withFont:nil withColor:nil forState:ASControlStateNormal]; + [_buttonNode setTitle:@"Some Bla" withFont:nil withColor:[UIColor orangeColor] forState:ASControlStateHighlighted]; + [_buttonNode addTarget:self action:@selector(buttonAction:) forControlEvents:ASControlNodeEventTouchUpInside]; + _buttonNode.sizingDelegate = self; return self; } @@ -47,6 +63,8 @@ [super viewDidLoad]; [self.view addSubnode:self.sizingNode]; + [self.view addSubnode:self.imageNode]; + [self.view addSubnode:self.buttonNode]; } - (void)viewWillAppear:(BOOL)animated @@ -54,7 +72,18 @@ [super viewWillAppear:animated]; // Initial size of sizing node - self.sizingNode.frame = CGRectMake(100, 100, 50, 50); + //self.sizingNode.frame = CGRectMake(100, 100, 50, 50); + + self.buttonNode.frame = CGRectMake(100, 100, 200, 10); + [self displayNodeDidInvalidateSize:self.buttonNode]; + + // Initial size for image node +// self.imageNode.frame = CGRectMake(50, 70, 100, 100); +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// self.imageNode.frame = CGRectMake(50, 70, 70, 50); +// //[self.imageNode setNeedsLayout]; +// //[self.imageNode setNeedsDisplay]; +// }); // Start some timer to chang ethe size randomly [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { @@ -66,6 +95,7 @@ { [super viewDidLayoutSubviews]; + // Update the sizing node layout [self updateNodeLayout]; } @@ -75,6 +105,12 @@ // to get the new size from the display node and update the frame based on the returned size - (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode { + if (displayNode == self.buttonNode) { + CGSize s = [self.buttonNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + self.buttonNode.frame = CGRectMake(100, 100, s.width, s.height); + return; + } + [self updateNodeLayout]; /*dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -118,6 +154,12 @@ } +#pragma mark - + +- (void)buttonAction:(id)sender +{ + NSLog(@"Button Sender"); +} #pragma mark - Rotation diff --git a/examples/ASViewController/Sample/SampleSizingNode.h b/examples/ASViewController/Sample/SampleSizingNode.h index d02b99b8db..57ecdb7df0 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.h +++ b/examples/ASViewController/Sample/SampleSizingNode.h @@ -8,11 +8,7 @@ #import -// ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers -@interface ASDisplayNodeSizingDelegate : NSObject -- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; -@end @interface SampleSizingNode : ASDisplayNode -@property (nonatomic, weak) id delegate; + @end diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m index e59a855758..a1767b12a9 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.m +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -13,6 +13,7 @@ @property (nonatomic, assign) NSInteger state; @property (nonatomic, strong) ASTextNode *textNode; +@property (nonatomic, strong) ASNetworkImageNode *imageNode; @end @implementation SampleSizingNode @@ -28,14 +29,33 @@ _textNode = [ASTextNode new]; _textNode.backgroundColor = [UIColor blueColor]; - //_textNode.autoresizingMask = UIViewAutoresizingNone; + + _imageNode = [ASNetworkImageNode new]; + _imageNode.URL = [NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Mont_Blanc_oct_2004.JPG/280px-Mont_Blanc_oct_2004.JPG"]; + _imageNode.backgroundColor = [UIColor brownColor]; + _imageNode.needsDisplayOnBoundsChange = YES; + _imageNode.style.height = ASDimensionMakeWithFraction(1.0); + _imageNode.style.width = ASDimensionMake(30.0); + _subnode = [ASDisplayNode new]; _subnode.backgroundColor = [UIColor redColor]; _subnode.automaticallyManagesSubnodes = YES; + + __weak __typeof(self) weakSelf = self; _subnode.layoutSpecBlock = ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; + ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:weakSelf.textNode]; + textInsetSpec.style.flexShrink = 1.0; + + return [ASStackLayoutSpec + stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[weakSelf.imageNode, textInsetSpec]]; + //return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit children:@[_imageNode, insetSpec]]; }; _state = 0; @@ -47,8 +67,11 @@ { [super didLoad]; - [self stateChanged]; + // Initial state + self.state = 0; + + // Simulate a state change of the node dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.state = 1; }); @@ -59,24 +82,35 @@ - (void)setState:(NSInteger)state { _state = state; - [self stateChanged]; -} - -- (void)stateChanged -{ - NSString *text = self.state == 0 ? @"Bla Bla" : @"Bla Blaa sd fkj as;l dkf"; + + // Set text based on state + NSString *text = state == 0 ? @"Bla Bla" : @"Bla Blaa sd fkj as;l dkf"; self.textNode.attributedText = [[NSAttributedString alloc] initWithString:text]; + //self.imageNode.style.width = state == 0 ? ASDimensionMake(30.0) : ASDimensionMake(50.0); + + // Let the root node know there can be a size change happened. If we will not call this the text node will just + // use it's own bounds and layout again in the next layout pass what can result in truncation + [self invalidateCalculatedLayoutSizingBlaBla]; +} + +// Invalidates the current layout and bubbles up the setNeedsLayout call to the root node. The root node will inform +// the sizing delegate that a size change did happen and it's up to it to decide if the bounds of the root node should +// change based on this request or not. If no change happened the layout will happen in all subnodes based on the current +// set bounds +- (void)invalidateCalculatedLayoutSizingBlaBla +{ // Invalidate the layout for now and bubble it up until the root node to let the size provider know that // that a size change could have happened // --> Do we even need to invalidate the layout? [self setNeedsLayout]; - // If someone calls `setNeedsLayout` we have to inform the sizing delegate of the root node to be able - // to let them now that a size change happened - if ([self.delegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { - [self.delegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; - } + // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able + // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy + /*if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { + [self.sizingDelegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; + }*/ + [self invalidateSize]; } diff --git a/examples/SocialAppLayout/Sample/PostNode.m b/examples/SocialAppLayout/Sample/PostNode.m index 3206b00d99..b4abad7a64 100644 --- a/examples/SocialAppLayout/Sample/PostNode.m +++ b/examples/SocialAppLayout/Sample/PostNode.m @@ -339,7 +339,7 @@ - (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { - [self setNeedsLayout]; + [self invalidateSize]; } @end From e7fccb23631fc49aa53f852d01aa8f13fdb45a4b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 11 Nov 2016 17:32:19 -0800 Subject: [PATCH 06/39] Improve ASButtonNode layout invalidation --- AsyncDisplayKit/ASButtonNode.mm | 5 ++--- examples/ASDKLayoutTransition/Sample/ViewController.m | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 0520e66ac3..a8200ce534 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -142,6 +142,7 @@ [self updateBackgroundImage]; [self updateImage]; [self updateTitle]; + [self invalidateSize]; } - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously @@ -172,7 +173,6 @@ if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { _imageNode.image = newImage; [self setNeedsLayout]; - [self invalidateSize]; } } @@ -196,7 +196,6 @@ _titleNode.attributedText = newTitle; self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; - [self invalidateSize]; } } @@ -220,7 +219,6 @@ if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { _backgroundImageNode.image = newImage; [self setNeedsLayout]; - [self invalidateSize]; } } @@ -532,6 +530,7 @@ - (void)layout { [super layout]; + _backgroundImageNode.hidden = (_backgroundImageNode.image == nil); _imageNode.hidden = (_imageNode.image == nil); _titleNode.hidden = (_titleNode.attributedText.length == 0); diff --git a/examples/ASDKLayoutTransition/Sample/ViewController.m b/examples/ASDKLayoutTransition/Sample/ViewController.m index 28f08b251c..eb15153b63 100644 --- a/examples/ASDKLayoutTransition/Sample/ViewController.m +++ b/examples/ASDKLayoutTransition/Sample/ViewController.m @@ -61,11 +61,7 @@ _buttonNode = [[ASButtonNode alloc] init]; [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateNormal]; - - // Note: Currently we have to set all the button properties to the same one as for ASControlStateNormal. Otherwise - // if the button is involved in the layout transition it would break the transition as it does a layout pass - // while changing the title. This needs and will be fixed in the future! - [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateHighlighted]; + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:[buttonColor colorWithAlphaComponent:0.5] forState:ASControlStateHighlighted]; // Some debug colors @@ -80,7 +76,7 @@ { [super didLoad]; - [self.buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchDown]; + [self.buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchUpInside]; } #pragma mark - Actions From f81c7a05b625cde00838920d071297ddf2c96e9d Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 11 Nov 2016 17:33:56 -0800 Subject: [PATCH 07/39] Reverse ASViewController project change --- .../Sample.xcodeproj/project.pbxproj | 1228 +++++++++++------ 1 file changed, 833 insertions(+), 395 deletions(-) diff --git a/examples/ASViewController/Sample.xcodeproj/project.pbxproj b/examples/ASViewController/Sample.xcodeproj/project.pbxproj index 1d11b3492e..eeef9f4c40 100644 --- a/examples/ASViewController/Sample.xcodeproj/project.pbxproj +++ b/examples/ASViewController/Sample.xcodeproj/project.pbxproj @@ -1,395 +1,833 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */; }; - 694993D21C8B334F00491CA5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D11C8B334F00491CA5 /* main.m */; }; - 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D41C8B334F00491CA5 /* AppDelegate.m */; }; - 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; - 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; - 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; - 698247E91DD51C8D004D1E37 /* SampleSizingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 698247E81DD51C8D004D1E37 /* SampleSizingNode.m */; }; - 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5211C8B3D30006FF548 /* DetailViewController.m */; }; - 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */; }; - 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 69DCA5271C8BE031006FF548 /* DetailCellNode.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 15AD337503831C4D33FF8B3A /* 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 = ""; }; - 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 694993D41C8B334F00491CA5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 694993D61C8B334F00491CA5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 694993D71C8B334F00491CA5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 698247E71DD51C8D004D1E37 /* SampleSizingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleSizingNode.h; sourceTree = ""; }; - 698247E81DD51C8D004D1E37 /* SampleSizingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SampleSizingNode.m; sourceTree = ""; }; - 69DCA5201C8B3D30006FF548 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; - 69DCA5211C8B3D30006FF548 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; - 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailRootNode.h; sourceTree = ""; }; - 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailRootNode.m; sourceTree = ""; }; - 69DCA5261C8BE031006FF548 /* DetailCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailCellNode.h; sourceTree = ""; }; - 69DCA5271C8BE031006FF548 /* DetailCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailCellNode.m; sourceTree = ""; }; - 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 694993CA1C8B334F00491CA5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 5CF3EF5E344946731D4F13F2 /* libPods-Sample.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 0DFDB4376BA084DAC7C1976E /* Pods */ = { - isa = PBXGroup; - children = ( - 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */, - 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - 478C8D7C412DCBDFE14640D8 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 694993C41C8B334F00491CA5 = { - isa = PBXGroup; - children = ( - 694993CF1C8B334F00491CA5 /* Sample */, - 694993CE1C8B334F00491CA5 /* Products */, - 0DFDB4376BA084DAC7C1976E /* Pods */, - 478C8D7C412DCBDFE14640D8 /* Frameworks */, - ); - sourceTree = ""; - }; - 694993CE1C8B334F00491CA5 /* Products */ = { - isa = PBXGroup; - children = ( - 694993CD1C8B334F00491CA5 /* Sample.app */, - ); - name = Products; - sourceTree = ""; - }; - 694993CF1C8B334F00491CA5 /* Sample */ = { - isa = PBXGroup; - children = ( - 694993D31C8B334F00491CA5 /* AppDelegate.h */, - 694993D41C8B334F00491CA5 /* AppDelegate.m */, - 694993D61C8B334F00491CA5 /* ViewController.h */, - 694993D71C8B334F00491CA5 /* ViewController.m */, - 69DCA5201C8B3D30006FF548 /* DetailViewController.h */, - 69DCA5211C8B3D30006FF548 /* DetailViewController.m */, - 69DCA5231C8BE01F006FF548 /* DetailRootNode.h */, - 69DCA5241C8BE01F006FF548 /* DetailRootNode.m */, - 69DCA5261C8BE031006FF548 /* DetailCellNode.h */, - 69DCA5271C8BE031006FF548 /* DetailCellNode.m */, - 694993DC1C8B334F00491CA5 /* Assets.xcassets */, - 694993D01C8B334F00491CA5 /* Supporting Files */, - 698247E71DD51C8D004D1E37 /* SampleSizingNode.h */, - 698247E81DD51C8D004D1E37 /* SampleSizingNode.m */, - ); - path = Sample; - sourceTree = ""; - }; - 694993D01C8B334F00491CA5 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 694993E11C8B334F00491CA5 /* Info.plist */, - 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */, - 694993D11C8B334F00491CA5 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 694993CC1C8B334F00491CA5 /* Sample */ = { - isa = PBXNativeTarget; - buildConfigurationList = 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */; - buildPhases = ( - 80035273449C25F4B2E1454F /* [CP] Check Pods Manifest.lock */, - 694993C91C8B334F00491CA5 /* Sources */, - 694993CA1C8B334F00491CA5 /* Frameworks */, - 694993CB1C8B334F00491CA5 /* Resources */, - 06EE2E0ABEB6289D4775A867 /* [CP] Copy Pods Resources */, - 23FC03B282CBD9014D868DF6 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Sample; - productName = Sample; - productReference = 694993CD1C8B334F00491CA5 /* Sample.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 694993C51C8B334F00491CA5 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0720; - ORGANIZATIONNAME = AsyncDisplayKit; - TargetAttributes = { - 694993CC1C8B334F00491CA5 = { - CreatedOnToolsVersion = 7.2.1; - }; - }; - }; - buildConfigurationList = 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 694993C41C8B334F00491CA5; - productRefGroup = 694993CE1C8B334F00491CA5 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 694993CC1C8B334F00491CA5 /* Sample */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 694993CB1C8B334F00491CA5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */, - 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 06EE2E0ABEB6289D4775A867 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 23FC03B282CBD9014D868DF6 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 80035273449C25F4B2E1454F /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 694993C91C8B334F00491CA5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, - 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, - 698247E91DD51C8D004D1E37 /* SampleSizingNode.m in Sources */, - 694993D21C8B334F00491CA5 /* main.m in Sources */, - 69DCA5221C8B3D30006FF548 /* DetailViewController.m in Sources */, - 69DCA5281C8BE031006FF548 /* DetailCellNode.m in Sources */, - 69DCA5251C8BE01F006FF548 /* DetailRootNode.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 694993DF1C8B334F00491CA5 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 694993E21C8B334F00491CA5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 694993E31C8B334F00491CA5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 694993E51C8B334F00491CA5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = Sample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 694993E61C8B334F00491CA5 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = Sample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = org.AsyncDisplayKit.Sample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 694993C81C8B334F00491CA5 /* Build configuration list for PBXProject "Sample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 694993E21C8B334F00491CA5 /* Debug */, - 694993E31C8B334F00491CA5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 694993E41C8B334F00491CA5 /* Build configuration list for PBXNativeTarget "Sample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 694993E51C8B334F00491CA5 /* Debug */, - 694993E61C8B334F00491CA5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 694993C51C8B334F00491CA5 /* Project object */; -} + + + + + archiveVersion + 1 + classes + + objectVersion + 46 + objects + + 06EE2E0ABEB6289D4775A867 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Copy Pods Resources + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" + + showEnvVarsInLog + 0 + + 0DFDB4376BA084DAC7C1976E + + children + + 15AD337503831C4D33FF8B3A + 97482F27BE2F7583EFE1BC2C + + isa + PBXGroup + name + Pods + sourceTree + <group> + + 15AD337503831C4D33FF8B3A + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods-Sample.debug.xcconfig + path + Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig + sourceTree + <group> + + 23FC03B282CBD9014D868DF6 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Embed Pods Frameworks + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" + + showEnvVarsInLog + 0 + + 465082D55CCF1B0CB1AEBACC + + explicitFileType + archive.ar + includeInIndex + 0 + isa + PBXFileReference + path + libPods-Sample.a + sourceTree + BUILT_PRODUCTS_DIR + + 478C8D7C412DCBDFE14640D8 + + children + + 465082D55CCF1B0CB1AEBACC + + isa + PBXGroup + name + Frameworks + sourceTree + <group> + + 5CF3EF5E344946731D4F13F2 + + fileRef + 465082D55CCF1B0CB1AEBACC + isa + PBXBuildFile + + 694993C41C8B334F00491CA5 + + children + + 694993CF1C8B334F00491CA5 + 694993CE1C8B334F00491CA5 + 0DFDB4376BA084DAC7C1976E + 478C8D7C412DCBDFE14640D8 + + isa + PBXGroup + sourceTree + <group> + + 694993C51C8B334F00491CA5 + + attributes + + LastUpgradeCheck + 0720 + ORGANIZATIONNAME + AsyncDisplayKit + TargetAttributes + + 694993CC1C8B334F00491CA5 + + CreatedOnToolsVersion + 7.2.1 + + + + buildConfigurationList + 694993C81C8B334F00491CA5 + compatibilityVersion + Xcode 3.2 + developmentRegion + English + hasScannedForEncodings + 0 + isa + PBXProject + knownRegions + + en + Base + + mainGroup + 694993C41C8B334F00491CA5 + productRefGroup + 694993CE1C8B334F00491CA5 + projectDirPath + + projectReferences + + projectRoot + + targets + + 694993CC1C8B334F00491CA5 + + + 694993C81C8B334F00491CA5 + + buildConfigurations + + 694993E21C8B334F00491CA5 + 694993E31C8B334F00491CA5 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 694993C91C8B334F00491CA5 + + buildActionMask + 2147483647 + files + + 694993D81C8B334F00491CA5 + 694993D51C8B334F00491CA5 + 694993D21C8B334F00491CA5 + 69DCA5221C8B3D30006FF548 + 69DCA5281C8BE031006FF548 + 69DCA5251C8BE01F006FF548 + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 694993CA1C8B334F00491CA5 + + buildActionMask + 2147483647 + files + + 5CF3EF5E344946731D4F13F2 + + isa + PBXFrameworksBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 694993CB1C8B334F00491CA5 + + buildActionMask + 2147483647 + files + + 694993E01C8B334F00491CA5 + 694993DD1C8B334F00491CA5 + + isa + PBXResourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 694993CC1C8B334F00491CA5 + + buildConfigurationList + 694993E41C8B334F00491CA5 + buildPhases + + 80035273449C25F4B2E1454F + 694993C91C8B334F00491CA5 + 694993CA1C8B334F00491CA5 + 694993CB1C8B334F00491CA5 + 06EE2E0ABEB6289D4775A867 + 23FC03B282CBD9014D868DF6 + + buildRules + + dependencies + + isa + PBXNativeTarget + name + Sample + productName + Sample + productReference + 694993CD1C8B334F00491CA5 + productType + com.apple.product-type.application + + 694993CD1C8B334F00491CA5 + + explicitFileType + wrapper.application + includeInIndex + 0 + isa + PBXFileReference + path + Sample.app + sourceTree + BUILT_PRODUCTS_DIR + + 694993CE1C8B334F00491CA5 + + children + + 694993CD1C8B334F00491CA5 + + isa + PBXGroup + name + Products + sourceTree + <group> + + 694993CF1C8B334F00491CA5 + + children + + 694993D31C8B334F00491CA5 + 694993D41C8B334F00491CA5 + 694993D61C8B334F00491CA5 + 694993D71C8B334F00491CA5 + 69DCA5201C8B3D30006FF548 + 69DCA5211C8B3D30006FF548 + 69DCA5231C8BE01F006FF548 + 69DCA5241C8BE01F006FF548 + 69DCA5261C8BE031006FF548 + 69DCA5271C8BE031006FF548 + 694993DC1C8B334F00491CA5 + 694993D01C8B334F00491CA5 + + isa + PBXGroup + path + Sample + sourceTree + <group> + + 694993D01C8B334F00491CA5 + + children + + 694993E11C8B334F00491CA5 + 694993DE1C8B334F00491CA5 + 694993D11C8B334F00491CA5 + + isa + PBXGroup + name + Supporting Files + sourceTree + <group> + + 694993D11C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + main.m + sourceTree + <group> + + 694993D21C8B334F00491CA5 + + fileRef + 694993D11C8B334F00491CA5 + isa + PBXBuildFile + + 694993D31C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + AppDelegate.h + sourceTree + <group> + + 694993D41C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + AppDelegate.m + sourceTree + <group> + + 694993D51C8B334F00491CA5 + + fileRef + 694993D41C8B334F00491CA5 + isa + PBXBuildFile + + 694993D61C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + ViewController.h + sourceTree + <group> + + 694993D71C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + ViewController.m + sourceTree + <group> + + 694993D81C8B334F00491CA5 + + fileRef + 694993D71C8B334F00491CA5 + isa + PBXBuildFile + + 694993DC1C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + folder.assetcatalog + path + Assets.xcassets + sourceTree + <group> + + 694993DD1C8B334F00491CA5 + + fileRef + 694993DC1C8B334F00491CA5 + isa + PBXBuildFile + + 694993DE1C8B334F00491CA5 + + children + + 694993DF1C8B334F00491CA5 + + isa + PBXVariantGroup + name + LaunchScreen.storyboard + sourceTree + <group> + + 694993DF1C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + file.storyboard + name + Base + path + Base.lproj/LaunchScreen.storyboard + sourceTree + <group> + + 694993E01C8B334F00491CA5 + + fileRef + 694993DE1C8B334F00491CA5 + isa + PBXBuildFile + + 694993E11C8B334F00491CA5 + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 694993E21C8B334F00491CA5 + + buildSettings + + ALWAYS_SEARCH_USER_PATHS + NO + CLANG_CXX_LANGUAGE_STANDARD + gnu++0x + CLANG_CXX_LIBRARY + libc++ + CLANG_ENABLE_MODULES + YES + CLANG_ENABLE_OBJC_ARC + YES + CLANG_WARN_BOOL_CONVERSION + YES + CLANG_WARN_CONSTANT_CONVERSION + YES + CLANG_WARN_DIRECT_OBJC_ISA_USAGE + YES_ERROR + CLANG_WARN_EMPTY_BODY + YES + CLANG_WARN_ENUM_CONVERSION + YES + CLANG_WARN_INT_CONVERSION + YES + CLANG_WARN_OBJC_ROOT_CLASS + YES_ERROR + CLANG_WARN_UNREACHABLE_CODE + YES + CLANG_WARN__DUPLICATE_METHOD_MATCH + YES + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + COPY_PHASE_STRIP + NO + DEBUG_INFORMATION_FORMAT + dwarf + ENABLE_STRICT_OBJC_MSGSEND + YES + ENABLE_TESTABILITY + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_DYNAMIC_NO_PIC + NO + GCC_NO_COMMON_BLOCKS + YES + GCC_OPTIMIZATION_LEVEL + 0 + GCC_PREPROCESSOR_DEFINITIONS + + DEBUG=1 + $(inherited) + + GCC_WARN_64_TO_32_BIT_CONVERSION + YES + GCC_WARN_ABOUT_RETURN_TYPE + YES_ERROR + GCC_WARN_UNDECLARED_SELECTOR + YES + GCC_WARN_UNINITIALIZED_AUTOS + YES_AGGRESSIVE + GCC_WARN_UNUSED_FUNCTION + YES + GCC_WARN_UNUSED_VARIABLE + YES + IPHONEOS_DEPLOYMENT_TARGET + 7.0 + MTL_ENABLE_DEBUG_INFO + YES + ONLY_ACTIVE_ARCH + YES + SDKROOT + iphoneos + + isa + XCBuildConfiguration + name + Debug + + 694993E31C8B334F00491CA5 + + buildSettings + + ALWAYS_SEARCH_USER_PATHS + NO + CLANG_CXX_LANGUAGE_STANDARD + gnu++0x + CLANG_CXX_LIBRARY + libc++ + CLANG_ENABLE_MODULES + YES + CLANG_ENABLE_OBJC_ARC + YES + CLANG_WARN_BOOL_CONVERSION + YES + CLANG_WARN_CONSTANT_CONVERSION + YES + CLANG_WARN_DIRECT_OBJC_ISA_USAGE + YES_ERROR + CLANG_WARN_EMPTY_BODY + YES + CLANG_WARN_ENUM_CONVERSION + YES + CLANG_WARN_INT_CONVERSION + YES + CLANG_WARN_OBJC_ROOT_CLASS + YES_ERROR + CLANG_WARN_UNREACHABLE_CODE + YES + CLANG_WARN__DUPLICATE_METHOD_MATCH + YES + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + COPY_PHASE_STRIP + NO + DEBUG_INFORMATION_FORMAT + dwarf-with-dsym + ENABLE_NS_ASSERTIONS + NO + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_NO_COMMON_BLOCKS + YES + GCC_WARN_64_TO_32_BIT_CONVERSION + YES + GCC_WARN_ABOUT_RETURN_TYPE + YES_ERROR + GCC_WARN_UNDECLARED_SELECTOR + YES + GCC_WARN_UNINITIALIZED_AUTOS + YES_AGGRESSIVE + GCC_WARN_UNUSED_FUNCTION + YES + GCC_WARN_UNUSED_VARIABLE + YES + IPHONEOS_DEPLOYMENT_TARGET + 7.0 + MTL_ENABLE_DEBUG_INFO + NO + SDKROOT + iphoneos + VALIDATE_PRODUCT + YES + + isa + XCBuildConfiguration + name + Release + + 694993E41C8B334F00491CA5 + + buildConfigurations + + 694993E51C8B334F00491CA5 + 694993E61C8B334F00491CA5 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 694993E51C8B334F00491CA5 + + baseConfigurationReference + 15AD337503831C4D33FF8B3A + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Sample/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_BUNDLE_IDENTIFIER + org.AsyncDisplayKit.Sample + PRODUCT_NAME + $(TARGET_NAME) + + isa + XCBuildConfiguration + name + Debug + + 694993E61C8B334F00491CA5 + + baseConfigurationReference + 97482F27BE2F7583EFE1BC2C + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Sample/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_BUNDLE_IDENTIFIER + org.AsyncDisplayKit.Sample + PRODUCT_NAME + $(TARGET_NAME) + + isa + XCBuildConfiguration + name + Release + + 69DCA5201C8B3D30006FF548 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + DetailViewController.h + sourceTree + <group> + + 69DCA5211C8B3D30006FF548 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + DetailViewController.m + sourceTree + <group> + + 69DCA5221C8B3D30006FF548 + + fileRef + 69DCA5211C8B3D30006FF548 + isa + PBXBuildFile + + 69DCA5231C8BE01F006FF548 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + DetailRootNode.h + sourceTree + <group> + + 69DCA5241C8BE01F006FF548 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + DetailRootNode.m + sourceTree + <group> + + 69DCA5251C8BE01F006FF548 + + fileRef + 69DCA5241C8BE01F006FF548 + isa + PBXBuildFile + + 69DCA5261C8BE031006FF548 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + DetailCellNode.h + sourceTree + <group> + + 69DCA5271C8BE031006FF548 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + DetailCellNode.m + sourceTree + <group> + + 69DCA5281C8BE031006FF548 + + fileRef + 69DCA5271C8BE031006FF548 + isa + PBXBuildFile + + 80035273449C25F4B2E1454F + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + Check Pods Manifest.lock + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null +if [[ $? != 0 ]] ; then + cat << EOM +error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. +EOM + exit 1 +fi + + showEnvVarsInLog + 0 + + 97482F27BE2F7583EFE1BC2C + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods-Sample.release.xcconfig + path + Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig + sourceTree + <group> + + + rootObject + 694993C51C8B334F00491CA5 + + From c819d716b4d88e845bc58dd37c1ead7ec96e954c Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 11 Nov 2016 18:14:54 -0800 Subject: [PATCH 08/39] Implement sizeToFit: --- AsyncDisplayKit/ASDisplayNode.h | 1 + AsyncDisplayKit/ASDisplayNode.mm | 30 +++++++++++++++++++ .../Sample/DetailViewController.m | 18 +++++++++-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index cb1594049a..f9ebca2af7 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -269,6 +269,7 @@ extern NSInteger const ASDefaultDrawingPriority; @property (nonatomic, readwrite, weak, nullable) id sizingDelegate; - (void)invalidateSize; +- (void)sizeToFit; - (CGSize)sizeThatFits:(CGSize)size; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index dbe83499b8..fb3e4cb5eb 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -730,6 +730,36 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) __instanceLock__.unlock(); } +- (void)sizeToFit +{ + ASDisplayNodeAssertThreadAffinity(self); + + __instanceLock__.lock(); + + [self setNeedsLayout]; + + CGSize maxSize = _supernode ? _supernode.bounds.size : CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); + CGSize size = [self sizeThatFits:maxSize]; + + CGRect oldBounds = self.bounds; + CGSize oldSize = oldBounds.size; + CGSize newSize = _calculatedDisplayNodeLayout->layout.size; + + if (! CGSizeEqualToSize(oldSize, newSize)) { + self.bounds = (CGRect){ oldBounds.origin, newSize }; + + // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint + // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. + CGPoint anchorPoint = self.anchorPoint; + CGPoint oldPosition = self.position; + CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; + CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; + self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); + } + + __instanceLock__.unlock(); +} + - (CGSize)sizeThatFits:(CGSize)size { return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, size)].size; diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index e246ae56ab..02bdceb2f9 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -74,7 +74,6 @@ // Initial size of sizing node //self.sizingNode.frame = CGRectMake(100, 100, 50, 50); - self.buttonNode.frame = CGRectMake(100, 100, 200, 10); [self displayNodeDidInvalidateSize:self.buttonNode]; // Initial size for image node @@ -95,19 +94,32 @@ { [super viewDidLayoutSubviews]; + [self updateButtonNodeLayout]; + // Update the sizing node layout [self updateNodeLayout]; } #pragma mark - Update the node based on the new size +- (void)updateButtonNodeLayout +{ + [self.buttonNode sizeToFit]; + self.buttonNode.frame = CGRectMake((self.view.bounds.size.width - self.buttonNode.bounds.size.width) / 2.0, + 100, + self.buttonNode.bounds.size.width, + self.buttonNode.bounds.size.height); + + //CGSize s = [self.buttonNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + //self.buttonNode.frame = CGRectMake(100, 100, s.width, s.height); +} + // The sizing delegate will get callbacks if the size did invalidate of the display node. It's the job of the delegate // to get the new size from the display node and update the frame based on the returned size - (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode { if (displayNode == self.buttonNode) { - CGSize s = [self.buttonNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; - self.buttonNode.frame = CGRectMake(100, 100, s.width, s.height); + [self updateButtonNodeLayout]; return; } From 0eb882bae56cbaf3d408bb7e53ad8d8c1729c0e0 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 13 Nov 2016 17:39:46 -0800 Subject: [PATCH 09/39] Some more changes --- AsyncDisplayKit/ASCellNode.mm | 24 +-- AsyncDisplayKit/ASCollectionView.mm | 4 +- AsyncDisplayKit/ASDisplayNode.h | 3 +- AsyncDisplayKit/ASDisplayNode.mm | 192 ++++++++---------- AsyncDisplayKit/ASImageNode.mm | 2 +- AsyncDisplayKit/ASTableView.mm | 5 +- AsyncDisplayKit/ASTextNode.mm | 7 +- AsyncDisplayKit/Details/_ASDisplayLayer.mm | 2 +- .../Private/ASDisplayNode+AsyncDisplay.mm | 2 +- .../Private/ASDisplayNodeInternal.h | 2 +- .../Sample/DetailViewController.m | 66 +++--- .../Sample/SampleSizingNode.m | 34 +++- examples/Kittens/Sample/KittenNode.mm | 4 +- 13 files changed, 181 insertions(+), 166 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 77270c3736..fee0e53935 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -29,7 +29,7 @@ #pragma mark - #pragma mark ASCellNode -@interface ASCellNode () +@interface ASCellNode () { ASDisplayNodeViewControllerBlock _viewControllerBlock; ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock; @@ -58,9 +58,6 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; - - // ASCellNode acts as sizing container for the node - self.sizingDelegate = self; return self; } @@ -121,19 +118,16 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init _viewControllerNode.frame = self.bounds; } -- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode +- (void)didInvalidateSize { - ASDN::MutexLocker l(__instanceLock__); + // TODO: coalesc: Ask the UITableView for the proper constrained size it can layout + CGSize oldSize = self.calculatedSize; + CGSize newSize = [self sizeThatFits:CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)]; - CGSize oldSize = self.calculatedSize; - ASLayout *layout = [self layoutThatFits:self.constrainedSizeForCalculatedLayout]; - - // TODO: Needs proper adjustment - CGRect f = self.frame; - f.size = layout.size; - self.frame = f; - - [self didRelayoutFromOldSize:oldSize toNewSize:layout.size]; + if (CGSizeEqualToSize(oldSize, newSize) == NO) { + self.frame = {self.frame.origin, newSize}; + [self didRelayoutFromOldSize:oldSize toNewSize:newSize]; + } } - (void)transitionLayoutWithAnimation:(BOOL)animated diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index cc870a9f79..8f4f3aa37e 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -765,7 +765,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { - return [[self nodeForItemAtIndexPath:indexPath] calculatedSize]; + ASDisplayNode *node = [self nodeForItemAtIndexPath:indexPath]; + [node layoutIfNeeded]; + return node.calculatedSize; } - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index f9ebca2af7..06078abaef 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -267,8 +267,9 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ -@property (nonatomic, readwrite, weak, nullable) id sizingDelegate; +//@property (nonatomic, readwrite, weak, nullable) id sizingDelegate; - (void)invalidateSize; +- (void)didInvalidateSize; - (void)sizeToFit; - (CGSize)sizeThatFits:(CGSize)size; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index fb3e4cb5eb..be1db2c911 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -709,14 +709,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) __instanceLock__.lock(); + // Mark the node for layout in the next layout pass [self invalidateCalculatedLayout]; // This is the root node. Let the delegate know that the size changed // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy - if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { - [self.sizingDelegate displayNodeDidInvalidateSize:self]; - } +// if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { +// [self.sizingDelegate displayNodeDidInvalidateSize:self]; +// } + + // Hook for subclasses to get size invalidation changes + [self didInvalidateSize]; if (_supernode) { ASDisplayNode *supernode = _supernode; @@ -727,9 +731,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return; } + // We are now at the root node trigger a layout pass + [self setNeedsLayout]; + __instanceLock__.unlock(); } +- (void)didInvalidateSize +{ + +} + - (void)sizeToFit { ASDisplayNodeAssertThreadAffinity(self); @@ -739,11 +751,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self setNeedsLayout]; CGSize maxSize = _supernode ? _supernode.bounds.size : CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - CGSize size = [self sizeThatFits:maxSize]; + CGSize newSize = [self sizeThatFits:maxSize]; CGRect oldBounds = self.bounds; CGSize oldSize = oldBounds.size; - CGSize newSize = _calculatedDisplayNodeLayout->layout.size; if (! CGSizeEqualToSize(oldSize, newSize)) { self.bounds = (CGRect){ oldBounds.origin, newSize }; @@ -777,74 +788,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDN::MutexLocker l(__instanceLock__); - if ([self shouldCalculateLayoutWithConstrainedSize:constrainedSize parentSize:parentSize] == NO) { + if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) { ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } - - [self cancelLayoutTransition]; - - BOOL didCreateNewContext = NO; - BOOL didOverrideExistingContext = NO; - BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); - ASLayoutElementContext context; - if (ASLayoutElementContextIsNull(ASLayoutElementGetCurrentContext())) { - context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); - ASLayoutElementSetCurrentContext(context); - didCreateNewContext = YES; - } else { - context = ASLayoutElementGetCurrentContext(); - if (context.needsVisualizeNode != shouldVisualizeLayout) { - context.needsVisualizeNode = shouldVisualizeLayout; - ASLayoutElementSetCurrentContext(context); - didOverrideExistingContext = YES; - } - } - - // Prepare for layout transition - auto previousLayout = _calculatedDisplayNodeLayout; - auto pendingLayout = std::make_shared( - [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, - parentSize - ); - - if (didCreateNewContext) { - ASLayoutElementClearCurrentContext(); - } else if (didOverrideExistingContext) { - context.needsVisualizeNode = !context.needsVisualizeNode; - ASLayoutElementSetCurrentContext(context); - } - - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:pendingLayout - previousLayout:previousLayout]; - - // Only complete the pending layout transition if the node is not a subnode of a node that is currently - // in a layout transition - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { - // Complete the pending layout transition immediately - [self _completePendingLayoutTransition]; - } - - ASDisplayNodeAssertNotNil(pendingLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] newLayout should not be nil! %@", self); - return pendingLayout->layout; -} - -- (BOOL)shouldCalculateLayoutWithConstrainedSize:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize -{ - ASDN::MutexLocker l(__instanceLock__); - - // Don't remeasure if in layout pending state and a new transition already started - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); - if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) { - return NO; - } - } - - // Check if display node layout is still valid - return _calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize) == NO; + + return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; } - (ASLayoutElementType)layoutElementType @@ -883,7 +832,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return; } - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; [self transitionLayoutWithSizeRange:_calculatedDisplayNodeLayout->constrainedSize animated:animated shouldMeasureAsync:shouldMeasureAsync @@ -898,7 +847,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { // Passed constrainedSize is the the same as the node's current constrained size it's a noop ASDisplayNodeAssertMainThread(); - if ([self shouldCalculateLayoutWithConstrainedSize:constrainedSize parentSize:constrainedSize.max] == NO) { + if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, constrainedSize.max)) { return; } @@ -1449,13 +1398,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self displayImmediately]; } +- (void)invalidateCalculatedLayout +{ + // This will cause the next layout pass to compute a new layout instead of returning + // the cached layout in case the constrained or parent size did not change + _calculatedDisplayNodeLayout->invalidate(); +} + - (void)__setNeedsLayout { ASDN::MutexLocker l(__instanceLock__); - // This will cause the next call to -layoutThatFits:parentSize: to compute a new layout instead of returning - // the cached layout in case the constrained or parent size did not change - _calculatedDisplayNodeLayout->invalidate(); + [self invalidateCalculatedLayout]; } /** @@ -1482,7 +1436,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [supernode __layoutIfNeeded]; } else { // Layout all subviews starting from the first node that needs layout - [self __layout]; + [self __layoutSublayers]; } } @@ -1498,13 +1452,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed. -- (void)__layout +- (void)__layoutSublayers { ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(__instanceLock__); - CGRect bounds = self.bounds; - - [self measureNodeWithBoundsIfNecessary:bounds]; + CGRect bounds = _threadSafeBounds; if (CGRectEqualToRect(bounds, CGRectZero)) { // Performing layout on a zero-bounds view often results in frame calculations @@ -1512,6 +1464,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // measureWithSizeRange: on subnodes to assert. return; } + + [self measureNodeWithBoundsIfNecessary:bounds]; // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer // was created @@ -1524,18 +1478,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self layoutDidFinish]; } +/// Needs to be called with lock held - (void)measureNodeWithBoundsIfNecessary:(CGRect)bounds { - BOOL supportsRangeManagedInterfaceState = NO; - BOOL hasDirtyLayout = NO; - CGSize calculatedLayoutSize = CGSizeZero; - { - ASDN::MutexLocker l(__instanceLock__); - supportsRangeManagedInterfaceState = [self supportsRangeManagedInterfaceState]; - hasDirtyLayout = _calculatedDisplayNodeLayout->isDirty(); - calculatedLayoutSize = _calculatedDisplayNodeLayout->layout.size; - } - // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of // the layout transition if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { @@ -1549,8 +1494,60 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // for the node using a size range equal to whatever bounds were provided to the node 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); - } else if (hasDirtyLayout || CGSizeEqualToSize(calculatedLayoutSize, bounds.size) == NO) { - [self layoutThatFits:ASSizeRangeMake(bounds.size)]; + return; + } + + if (_calculatedDisplayNodeLayout->isDirty() || + CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size) == NO) + { + [self cancelLayoutTransition]; + + BOOL didCreateNewContext = NO; + BOOL didOverrideExistingContext = NO; + BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); + ASLayoutElementContext context; + if (ASLayoutElementContextIsNull(ASLayoutElementGetCurrentContext())) { + context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); + ASLayoutElementSetCurrentContext(context); + didCreateNewContext = YES; + } else { + context = ASLayoutElementGetCurrentContext(); + if (context.needsVisualizeNode != shouldVisualizeLayout) { + context.needsVisualizeNode = shouldVisualizeLayout; + ASLayoutElementSetCurrentContext(context); + didOverrideExistingContext = YES; + } + } + + // Prepare for layout transition + CGSize parentSize = bounds.size; + ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); + auto previousLayout = _calculatedDisplayNodeLayout; + auto pendingLayout = std::make_shared( + [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], + constrainedSize, + parentSize + ); + + if (didCreateNewContext) { + ASLayoutElementClearCurrentContext(); + } else if (didOverrideExistingContext) { + context.needsVisualizeNode = !context.needsVisualizeNode; + ASLayoutElementSetCurrentContext(context); + } + + ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendintLayout->layout should not be nil! %@", self); + + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:pendingLayout + previousLayout:previousLayout]; + + // Only complete the pending layout transition if the node is not a subnode of a node that is currently + // in a layout transition + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { + // Complete the pending layout transition immediately + [self _completePendingLayoutTransition]; + } } } @@ -2680,15 +2677,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return nil; } -- (void)invalidateCalculatedLayout -{ - ASDN::MutexLocker l(__instanceLock__); - - // This will cause the next call to -layoutThatFits:parentSize: to compute a new layout instead of returning - // the cached layout in case the constrained or parent size did not change - _calculatedDisplayNodeLayout->invalidate(); -} - - (void)__didLoad { ASDN::MutexLocker l(__instanceLock__); @@ -3730,7 +3718,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; { // Deprecated preferredFrameSize just calls through to set width and height self.style.preferredSize = preferredFrameSize; - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; } - (CGSize)preferredFrameSize diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index bdc2180db1..2678a7e8a9 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -204,7 +204,7 @@ struct ASImageNodeDrawParameters { if (!ASObjectIsEqual(_image, image)) { _image = image; - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; if (image) { [self setNeedsDisplay]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index ec1d8a3019..7495a3952e 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -751,6 +751,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; + [node layoutIfNeeded]; return node.calculatedSize.height; } @@ -1509,8 +1510,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - _ASTableViewCellDelegate -#pragma mark - _ASTableViewCellDelegate - - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell { ASCellNode *node = tableViewCell.node; @@ -1525,7 +1524,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Normally the content view width equals to the constrained size width (which equals to the table view width). // If there is a mismatch between these values, for example after the table view entered or left editing mode, // content view width is preferred and used to re-measure the cell node. - if (contentViewWidth != constrainedSize.max.width) { + if (CGSizeEqualToSize(node.calculatedSize, CGSizeZero) == NO && contentViewWidth != constrainedSize.max.width) { constrainedSize.min.width = contentViewWidth; constrainedSize.max.width = contentViewWidth; diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index f4cacda904..897088149e 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -330,8 +330,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset); if (needsUpdate) { _textContainerInset = textContainerInset; - [self invalidateCalculatedLayout]; - [self setNeedsLayout]; + [self invalidateSize]; } } @@ -487,7 +486,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } // Tell the display node superclasses that the cached layout is incorrect now - [self invalidateCalculatedLayout]; + [self invalidateSize]; // Force display to create renderer with new size and redisplay with new string [self setNeedsDisplay]; @@ -510,7 +509,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _exclusionPaths = [exclusionPaths copy]; [self _invalidateRenderer]; - [self invalidateCalculatedLayout]; + [self invalidateSize]; [self setNeedsDisplay]; } diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index dd7f70ee8f..1a49396825 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -135,7 +135,7 @@ ASDisplayNodeAssertMainThread(); [super layoutSublayers]; - [self.asyncdisplaykit_node __layout]; + [self.asyncdisplaykit_node __layoutSublayers]; } - (void)setNeedsDisplay diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index 484b6b76a9..4ee4ad0d3c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -52,7 +52,7 @@ // if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { - [self __layout]; + [self __layoutSublayers]; } // Capture these outside the display block so they are retained. diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index c26eb1c014..dc4f0eed81 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -212,7 +212,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (void)__setNeedsDisplay; -- (void)__layout; +- (void)__layoutSublayers; - (void)__setSupernode:(ASDisplayNode *)supernode; /** diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index 02bdceb2f9..a838ba645d 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -21,7 +21,7 @@ #import "DetailRootNode.h" #import "SampleSizingNode.h" -@interface DetailViewController () +@interface DetailViewController ()// @property (strong, nonatomic) SampleSizingNode *sizingNode; @property (strong, nonatomic) ASNetworkImageNode *imageNode; @@ -40,7 +40,7 @@ // Set the sizing delegate of the root node to the container _sizingNode = [SampleSizingNode new]; _sizingNode.autoresizingMask = UIViewAutoresizingNone; - _sizingNode.sizingDelegate = self; + //_sizingNode.sizingDelegate = self; _imageNode = [ASNetworkImageNode new]; _imageNode.needsDisplayOnBoundsChange = YES; @@ -53,7 +53,7 @@ [_buttonNode setTitle:@"Some Title" withFont:nil withColor:nil forState:ASControlStateNormal]; [_buttonNode setTitle:@"Some Bla" withFont:nil withColor:[UIColor orangeColor] forState:ASControlStateHighlighted]; [_buttonNode addTarget:self action:@selector(buttonAction:) forControlEvents:ASControlNodeEventTouchUpInside]; - _buttonNode.sizingDelegate = self; + //_buttonNode.sizingDelegate = self; return self; } @@ -74,7 +74,7 @@ // Initial size of sizing node //self.sizingNode.frame = CGRectMake(100, 100, 50, 50); - [self displayNodeDidInvalidateSize:self.buttonNode]; + //[self displayNodeDidInvalidateSize:self.buttonNode]; // Initial size for image node // self.imageNode.frame = CGRectMake(50, 70, 100, 100); @@ -94,6 +94,7 @@ { [super viewDidLayoutSubviews]; + // Updat the sizing for the button node [self updateButtonNodeLayout]; // Update the sizing node layout @@ -116,23 +117,23 @@ // The sizing delegate will get callbacks if the size did invalidate of the display node. It's the job of the delegate // to get the new size from the display node and update the frame based on the returned size -- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode -{ - if (displayNode == self.buttonNode) { - [self updateButtonNodeLayout]; - return; - } - - [self updateNodeLayout]; - - /*dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self updateNodeLayoutRandom]; - });*/ - - /*[NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { - [self updateNodeLayoutRandom]; - }];*/ -} +//- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode +//{ +// if (displayNode == self.buttonNode) { +// [self updateButtonNodeLayout]; +// return; +// } +// +// [self updateNodeLayout]; +// +// /*dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +// [self updateNodeLayoutRandom]; +// });*/ +// +// /*[NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { +// [self updateNodeLayoutRandom]; +// }];*/ +//} - (void)updateNodeLayout { @@ -140,29 +141,34 @@ //return; // Use the bounds of the view and get the fitting size // This does not have any side effects, but can be called on the main thread without any problems - CGSize size = [self.sizingNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, 100.0)]; + CGSize size = [self.sizingNode sizeThatFits:CGSizeMake(INFINITY, 100.0)]; //size.width -= 10; //[self.sizingNode setNeedsLayout]; - self.sizingNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, - (self.view.bounds.size.height - size.height) / 2.0, - size.width, size.height); + self.sizingNode.frame = CGRectMake((CGRectGetWidth(self.view.bounds) - size.width) / 2.0, + (CGRectGetHeight(self.view.bounds) - size.height) / 2.0, + size.width, + size.height); //dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // Decrease the frame a bit self.sizingNode.frame = CGRectInset(self.sizingNode.frame, 10, 10); //}); } - (void)updateNodeLayoutRandom { + CGRect bounds = self.view.bounds; + // Pick a randome width and height and set the frame of the node CGSize size = CGSizeZero; - size.width = arc4random_uniform(self.view.bounds.size.width); - size.height = arc4random_uniform(self.view.bounds.size.height); + size.width = arc4random_uniform(CGRectGetWidth(bounds)); + size.height = arc4random_uniform(CGRectGetHeight(bounds)); //[self.sizingNode setNeedsLayout]; - self.sizingNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, - (self.view.bounds.size.height - size.height) / 2.0, - size.width, size.height); + self.sizingNode.frame = CGRectMake((CGRectGetWidth(bounds) - size.width) / 2.0, + (CGRectGetHeight(bounds) - size.height) / 2.0, + size.width, + size.height); } diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m index a1767b12a9..674b545b09 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.m +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -8,10 +8,27 @@ #import "SampleSizingNode.h" +@interface SampleSizingNodeSubnode : ASDisplayNode +@property (strong, nonatomic) ASTextNode *textNode; +@end + +@implementation SampleSizingNodeSubnode + +- (void)layout +{ + [super layout]; + + // Manual layout after the normal layout engine did it's job + // Calculated size can be used after the layout spec pass happened + //self.textNode.frame = CGRectMake(self.textNode.frame.origin.x, self.textNode.frame.origin.y, self.textNode.calculatedSize.width, 20); +} + +@end + @interface SampleSizingNode () -@property (nonatomic, strong) ASDisplayNode *subnode; @property (nonatomic, assign) NSInteger state; +@property (nonatomic, strong) SampleSizingNodeSubnode *subnode; @property (nonatomic, strong) ASTextNode *textNode; @property (nonatomic, strong) ASNetworkImageNode *imageNode; @end @@ -35,13 +52,15 @@ _imageNode.backgroundColor = [UIColor brownColor]; _imageNode.needsDisplayOnBoundsChange = YES; _imageNode.style.height = ASDimensionMakeWithFraction(1.0); - _imageNode.style.width = ASDimensionMake(30.0); + _imageNode.style.width = ASDimensionMake(50.0); - _subnode = [ASDisplayNode new]; + _subnode = [SampleSizingNodeSubnode new]; + _subnode.textNode = _textNode; _subnode.backgroundColor = [UIColor redColor]; _subnode.automaticallyManagesSubnodes = YES; + // Layout description via layoutSpecBlock __weak __typeof(self) weakSelf = self; _subnode.layoutSpecBlock = ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { @@ -113,7 +132,6 @@ [self invalidateSize]; } - #pragma mark - ASDisplayNode - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize @@ -129,5 +147,13 @@ child:self.subnode]; } +- (void)layout +{ + [super layout]; + + // Layout after the official layout pass happened + //self.subnode.frame = CGRectMake(self.subnode.frame.origin.x, self.subnode.frame.origin.y, 100, self.calculatedSize.height); +} + @end diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 67e586b4a7..42a94a055a 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -201,7 +201,7 @@ static const CGFloat kInnerPadding = 10.0f; - (void)toggleImageEnlargement { _isImageEnlarged = !_isImageEnlarged; - [self setNeedsLayout]; + [self invalidateSize]; } - (void)toggleNodesSwap @@ -211,7 +211,7 @@ static const CGFloat kInnerPadding = 10.0f; [UIView animateWithDuration:0.15 animations:^{ self.alpha = 0; } completion:^(BOOL finished) { - [self setNeedsLayout]; + [self invalidateSize]; [self.view layoutIfNeeded]; [UIView animateWithDuration:0.15 animations:^{ From dcc54c0d366f04dbccb4eb57aff3ff298bc20ab8 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 14 Nov 2016 10:10:47 -0800 Subject: [PATCH 10/39] Proper relayout if size changes --- AsyncDisplayKit/ASCellNode+Internal.h | 6 ++++++ AsyncDisplayKit/ASCellNode.mm | 11 +++++++++-- AsyncDisplayKit/ASCollectionView.mm | 5 +++++ AsyncDisplayKit/ASTableView.mm | 5 +++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index 99066aa123..d2ebd4e27c 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -29,6 +29,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; +/// Returns the constrained size for the given node +- (ASSizeRange)constrainedSizeForNode:(ASCellNode *)node; + +// TODO: coalesc: Maybe we should push the relayout of nodes to the table / collection node? +//- (void)relayoutNodeIfNeecessary:(ASCellNode *)node; + /* * Methods to be called whenever the selection or highlight state changes * on ASCellNode. UIKit internally stores these values to update reusable cells. diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index fee0e53935..27e5cb1481 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -120,9 +120,8 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init - (void)didInvalidateSize { - // TODO: coalesc: Ask the UITableView for the proper constrained size it can layout CGSize oldSize = self.calculatedSize; - CGSize newSize = [self sizeThatFits:CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)]; + CGSize newSize = [self layoutThatFits:[self constrainedSize]].size; if (CGSizeEqualToSize(oldSize, newSize) == NO) { self.frame = {self.frame.origin, newSize}; @@ -174,6 +173,14 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init } } +- (ASSizeRange)constrainedSize +{ + if (_interactionDelegate != nil) { + return [_interactionDelegate constrainedSizeForNode:self]; + } + return ASSizeRangeMake(CGSizeZero, CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)); +} + - (void)setSelected:(BOOL)selected { if (_selected != selected) { diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 8f4f3aa37e..81132202d7 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1579,6 +1579,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +- (ASSizeRange)constrainedSizeForNode:(ASCellNode *)node +{ + return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:[self indexPathForNode:node]]; +} + - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 7495a3952e..32ca49ce81 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1568,6 +1568,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (ASSizeRange)constrainedSizeForNode:(ASCellNode *)node +{ + return [self dataController:self.dataController constrainedSizeForNodeAtIndexPath:[self indexPathForNode:node]]; +} + - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); From b0eb2f81c2b34ccd4cf639ad7c882688509986a2 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 14 Nov 2016 10:53:26 -0800 Subject: [PATCH 11/39] Cleanup --- AsyncDisplayKit/ASButtonNode.mm | 2 -- AsyncDisplayKit/ASCellNode.mm | 15 ++++++--------- AsyncDisplayKit/ASDisplayNode.h | 16 +--------------- AsyncDisplayKit/ASDisplayNode.mm | 7 ------- .../ASViewController/Sample/DetailRootNode.m | 2 -- 5 files changed, 7 insertions(+), 35 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index a8200ce534..bdd8589bf6 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -235,7 +235,6 @@ return; _contentSpacing = contentSpacing; - [self setNeedsLayout]; [self invalidateSize]; } @@ -252,7 +251,6 @@ return; _laysOutHorizontally = laysOutHorizontally; - [self setNeedsLayout]; [self invalidateSize]; } diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 27e5cb1481..dd7636fa09 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -121,7 +121,12 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init - (void)didInvalidateSize { CGSize oldSize = self.calculatedSize; - CGSize newSize = [self layoutThatFits:[self constrainedSize]].size; + + ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)); + if (_interactionDelegate != nil) { + constrainedSize = [_interactionDelegate constrainedSizeForNode:self]; + } + CGSize newSize = [self layoutThatFits:constrainedSize].size; if (CGSizeEqualToSize(oldSize, newSize) == NO) { self.frame = {self.frame.origin, newSize}; @@ -173,14 +178,6 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init } } -- (ASSizeRange)constrainedSize -{ - if (_interactionDelegate != nil) { - return [_interactionDelegate constrainedSizeForNode:self]; - } - return ASSizeRangeMake(CGSizeZero, CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)); -} - - (void)setSelected:(BOOL)selected { if (_selected != selected) { diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 06078abaef..d6ecce6229 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -26,19 +26,6 @@ NS_ASSUME_NONNULL_BEGIN @class ASDisplayNode; -// TODO: Extract to ASDisplayNode+Layout.h -// ASDisplayNodeSizingDelegate / ASDisplayNodeSizingHandlers -@protocol ASDisplayNodeSizingDelegate -@required -/** - Called after the display node state update happened (layout invlidation) that could lead to a - - The delegate can use this callback to appropriately resize the node frame to fit the new - node size. The node will not resize itself. - */ -- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode; -@end - /** * UIView creation block. Used to create the backing view of a new display node. */ @@ -267,11 +254,10 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ -//@property (nonatomic, readwrite, weak, nullable) id sizingDelegate; +// TODO: coalesc: Documentation - (void)invalidateSize; - (void)didInvalidateSize; - (void)sizeToFit; - - (CGSize)sizeThatFits:(CGSize)size; /** diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index be1db2c911..3afac750ea 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -712,13 +712,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Mark the node for layout in the next layout pass [self invalidateCalculatedLayout]; - // This is the root node. Let the delegate know that the size changed - // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able - // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy -// if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { -// [self.sizingDelegate displayNodeDidInvalidateSize:self]; -// } - // Hook for subclasses to get size invalidation changes [self didInvalidateSize]; diff --git a/examples/ASViewController/Sample/DetailRootNode.m b/examples/ASViewController/Sample/DetailRootNode.m index 11091cea05..333af36e6c 100644 --- a/examples/ASViewController/Sample/DetailRootNode.m +++ b/examples/ASViewController/Sample/DetailRootNode.m @@ -28,8 +28,6 @@ static const NSInteger kImageHeight = 200; @property (nonatomic, copy) NSString *imageCategory; @property (nonatomic, strong) ASCollectionNode *collectionNode; -@property (nonatomic, strong) ASDisplayNode *backgroundNode; - @end From 5d84f21fda9298641976bd48c5b74a4370e78353 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 14 Nov 2016 11:21:48 -0800 Subject: [PATCH 12/39] Add sizeThatFits: to _ASDisplayView --- AsyncDisplayKit/Details/_ASDisplayView.mm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 4068d5e4a8..6b4e90963e 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -202,6 +202,15 @@ #endif } +- (CGSize)sizeThatFits:(CGSize)size +{ + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + if (node) { + return [node sizeThatFits:size]; + } + return [super sizeThatFits:size]; +} + - (void)setNeedsDisplay { ASDisplayNodeAssertMainThread(); From 8f079bd1ca4031466f6eee738c5dada3d9354388 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 14 Nov 2016 14:28:40 -0800 Subject: [PATCH 13/39] Fix some tests --- .../ASDisplayNodeImplicitHierarchyTests.m | 54 ++++++++++++++----- .../ASDisplayNodeSnapshotTests.m | 2 +- AsyncDisplayKitTests/ASDisplayNodeTests.m | 12 +++-- .../ASDisplayNodeTestsHelper.h | 6 +++ .../ASDisplayNodeTestsHelper.m | 11 ++++ .../ASImageNodeSnapshotTests.m | 12 +++-- .../ASLayoutSpecSnapshotTestsHelper.m | 6 ++- AsyncDisplayKitTests/ASSnapshotTestCase.h | 1 + AsyncDisplayKitTests/ASSnapshotTestCase.m | 2 - .../ASTextNodeSnapshotTests.m | 12 ++--- 10 files changed, 85 insertions(+), 33 deletions(-) diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 6623c55205..9c5ee21c1d 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -12,6 +12,7 @@ #import +#import "ASDisplayNodeTestsHelper.h" #import "ASDisplayNode.h" #import "ASDisplayNode+Beta.h" #import "ASDisplayNode+Subclasses.h" @@ -88,7 +89,10 @@ return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, node5]]; }; - [node layoutThatFits:ASSizeRangeMake(CGSizeZero)]; + + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + [node layoutIfNeeded]; + XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); XCTAssertEqual(node.subnodes[2], node3); @@ -122,13 +126,14 @@ } }; - [node layoutThatFits:ASSizeRangeMake(CGSizeZero)]; + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); node.layoutState = @2; - [node invalidateCalculatedLayout]; - [node layoutThatFits:ASSizeRangeMake(CGSizeZero)]; + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node3); @@ -170,10 +175,12 @@ - (void)testMeasurementInBackgroundThreadWithLoadedNode { + const CGSize kNodeSize = CGSizeMake(100, 100); ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.style.preferredSize = kNodeSize; node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; @@ -191,17 +198,34 @@ dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [node layoutThatFits:ASSizeRangeMake(CGSizeZero)]; - XCTAssertEqual(node.subnodes[0], node1); - - node.layoutState = @2; - [node invalidateCalculatedLayout]; - [node layoutThatFits:ASSizeRangeMake(CGSizeZero)]; + // Measurement happens in the background + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); // Dispatch back to the main thread to let the insertion / deletion of subnodes happening dispatch_async(dispatch_get_main_queue(), ^{ - XCTAssertEqual(node.subnodes[0], node2); - [expectation fulfill]; + + // Layout on main + [node layoutIfNeeded]; + XCTAssertEqual(node.subnodes[0], node1); + + dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + // Change state and measure in the background + node.layoutState = @2; + [node setNeedsLayout]; + + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + + // Dispatch back to the main thread to let the insertion / deletion of subnodes happening + dispatch_async(dispatch_get_main_queue(), ^{ + + // Layout on main again + [node layoutIfNeeded]; + XCTAssertEqual(node.subnodes[0], node2); + + [expectation fulfill]; + }); + }); }); }); @@ -214,12 +238,13 @@ - (void)testTransitionLayoutWithAnimationWithLoadedNodes { + const CGSize kNodeSize = CGSizeMake(100, 100); ASDisplayNode *node1 = [[ASDisplayNode alloc] init]; ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; node.automaticallyManagesSubnodes = YES; - + node.style.preferredSize = kNodeSize; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; if ([strongNode.layoutState isEqualToNumber:@1]) { @@ -236,7 +261,8 @@ XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; - [node layoutThatFits:ASSizeRangeMake(CGSizeZero)]; + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); node.layoutState = @2; diff --git a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m index 2c2286b00a..763237003d 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m @@ -28,8 +28,8 @@ node.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5, 5, 5, 5) child:subnode]; }; - [node layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))]; + ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); ASSnapshotVerifyNode(node, nil); } diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 126e5c3806..c68e699381 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -2166,8 +2166,10 @@ static bool stringContainsPointer(NSString *description, id p) { // The inset spec here is crucial. If the nodes themselves are children, it passed before the fix. return [ASOverlayLayoutSpec overlayLayoutSpecWithChild:[ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:underlay] overlay:overlay]; }; - [node layoutThatFits:ASSizeRangeMake(CGSizeMake(100, 100))]; - node.frame = (CGRect){ .size = node.calculatedSize }; + + ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); + [node layoutIfNeeded]; + NSInteger underlayIndex = [node.subnodes indexOfObjectIdenticalTo:underlay]; NSInteger overlayIndex = [node.subnodes indexOfObjectIdenticalTo:overlay]; XCTAssertLessThan(underlayIndex, overlayIndex); @@ -2185,8 +2187,10 @@ static bool stringContainsPointer(NSString *description, id p) { // The inset spec here is crucial. If the nodes themselves are children, it passed before the fix. return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:overlay background:[ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:underlay]]; }; - [node layoutThatFits:ASSizeRangeMake(CGSizeMake(100, 100))]; - node.frame = (CGRect){ .size = node.calculatedSize }; + + ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); + [node layoutIfNeeded]; + NSInteger underlayIndex = [node.subnodes indexOfObjectIdenticalTo:underlay]; NSInteger overlayIndex = [node.subnodes indexOfObjectIdenticalTo:overlay]; XCTAssertLessThan(underlayIndex, overlayIndex); diff --git a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h index 7f1ad0b383..664bf30b77 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h +++ b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.h @@ -9,7 +9,13 @@ // #import +#import "ASDimension.h" + +@class ASDisplayNode; typedef BOOL (^as_condition_block_t)(void); BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block); + +void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size); +void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange); diff --git a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m index 728ffec66f..70f9502ac8 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m @@ -9,6 +9,8 @@ // #import "ASDisplayNodeTestsHelper.h" +#import "ASDisplayNode.h" +#import "ASLayout.h" #import @@ -41,3 +43,12 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block) } return passed; } + +void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size) { + CGSize sizeThatFits = [node sizeThatFits:size]; + node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; +} +void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) { + CGSize sizeThatFits = [node layoutThatFits:sizeRange].size; + node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; +} diff --git a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m index 55706583a5..ac53e3cd4e 100644 --- a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m @@ -30,7 +30,7 @@ // trivial test case to ensure ASSnapshotTestCase works ASImageNode *imageNode = [[ASImageNode alloc] init]; imageNode.image = [self testImage]; - [imageNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))]; + ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(100, 100)); ASSnapshotVerifyNode(imageNode, nil); } @@ -46,12 +46,14 @@ // Snapshot testing requires that node is formally laid out. imageNode.style.width = ASDimensionMake(forcedImageSize.width); imageNode.style.height = ASDimensionMake(forcedImageSize.height); - [imageNode layoutThatFits:ASSizeRangeMake(CGSizeZero, forcedImageSize)]; + ASDisplayNodeSizeToFitSize(imageNode, forcedImageSize); + [imageNode layoutIfNeeded]; ASSnapshotVerifyNode(imageNode, @"first"); imageNode.style.width = ASDimensionMake(200); imageNode.style.height = ASDimensionMake(200); - [imageNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(200, 200))]; + ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(200, 200)); + [imageNode layoutIfNeeded]; ASSnapshotVerifyNode(imageNode, @"second"); @@ -66,7 +68,7 @@ UIImage *tinted = ASImageNodeTintColorModificationBlock([UIColor redColor])(test); ASImageNode *node = [[ASImageNode alloc] init]; node.image = tinted; - [node layoutThatFits:ASSizeRangeMake(test.size)]; + ASDisplayNodeSizeToFitSize(node, test.size); ASSnapshotVerifyNode(node, nil); } @@ -81,7 +83,7 @@ UIImage *rounded = ASImageNodeRoundBorderModificationBlock(2, [UIColor redColor])(result); ASImageNode *node = [[ASImageNode alloc] init]; node.image = rounded; - [node layoutThatFits:ASSizeRangeMake(rounded.size)]; + ASDisplayNodeSizeToFitSize(node, rounded.size); ASSnapshotVerifyNode(node, nil); } diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m index e2d908d012..90a40f988c 100644 --- a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m +++ b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m @@ -39,7 +39,7 @@ node.layoutSpecUnderTest = layoutSpec; - [node layoutThatFits:sizeRange]; + ASDisplayNodeSizeToFitSizeRange(node, sizeRange); ASSnapshotVerifyNode(node, identifier); } @@ -56,6 +56,10 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { + // As the layout spec under test can be used multiple times we set the isMutable to NO in here. This should only + // be done in tests and never in production codes + _layoutSpecUnderTest.isMutable = YES; + return _layoutSpecUnderTest; } diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.h b/AsyncDisplayKitTests/ASSnapshotTestCase.h index c89fc79af4..d3f6791d86 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.h +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.h @@ -9,6 +9,7 @@ // #import +#import "ASDisplayNodeTestsHelper.h" @class ASDisplayNode; diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.m b/AsyncDisplayKitTests/ASSnapshotTestCase.m index 3bcfb67293..301ed923fe 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.m +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.m @@ -37,8 +37,6 @@ NSOrderedSet *ASSnapshotTestCaseDefaultSuffixes(void) + (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node { - ASDisplayNodeAssertNotNil(node.calculatedLayout, @"Node %@ must be measured before it is rendered.", node); - node.bounds = (CGRect) { .size = node.calculatedSize }; ASDisplayNodePerformBlockOnEveryNode(nil, node, YES, ^(ASDisplayNode * _Nonnull node) { [node.layer setNeedsDisplay]; }); diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index 811a967465..29d4bd4319 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -11,7 +11,6 @@ #import "ASSnapshotTestCase.h" #import -#import "ASLayout.h" @interface ASTextNodeSnapshotTests : ASSnapshotTestCase @@ -26,7 +25,8 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + textNode.frame = CGRectMake(0, 0, layout.size.width, layout.size.height); ASSnapshotVerifyNode(textNode, nil); } @@ -63,8 +63,8 @@ attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5); - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; - textNode.frame = CGRectMake(50, 50, textNode.calculatedSize.width, textNode.calculatedSize.height); + ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))]; + textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height); [backgroundView addSubview:textNode.view]; backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); @@ -90,7 +90,7 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }]; // Set exclusion paths to trigger slow path textNode.exclusionPaths = @[ [UIBezierPath bezierPath] ]; - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))]; + ASDisplayNodeSizeToFitSize(textNode, CGSizeMake(100, 50)); ASSnapshotVerifyNode(textNode, nil); } @@ -102,7 +102,7 @@ textNode.shadowOpacity = 0.3; textNode.shadowRadius = 3; textNode.shadowOffset = CGSizeMake(0, 1); - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + ASDisplayNodeSizeToFitSize(textNode, CGSizeMake(INFINITY, INFINITY)); ASSnapshotVerifyNode(textNode, nil); } From d21a512907c73e50e8d9def82f0b2d32aa08884b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 14 Nov 2016 16:57:00 -0800 Subject: [PATCH 14/39] Some commit --- AsyncDisplayKit/ASDisplayNode.mm | 119 +++++++++++------- .../Private/ASDisplayNodeInternal.h | 1 + .../ASTextNodeSnapshotTests.m | 9 +- 3 files changed, 80 insertions(+), 49 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 3afac750ea..b3b8081fb7 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -315,6 +315,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _environmentState = ASEnvironmentStateMakeDefault(); _calculatedDisplayNodeLayout = std::make_shared(); + _pendingDisplayNodeLayout = std::make_shared(); _defaultLayoutTransitionDuration = 0.2; _defaultLayoutTransitionDelay = 0.0; @@ -786,7 +787,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } - return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; + _pendingDisplayNodeLayout = std::make_shared( + [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], + constrainedSize, + parentSize + ); + ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); + return _pendingDisplayNodeLayout->layout; } - (ASLayoutElementType)layoutElementType @@ -1396,6 +1403,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // This will cause the next layout pass to compute a new layout instead of returning // the cached layout in case the constrained or parent size did not change _calculatedDisplayNodeLayout->invalidate(); + _pendingDisplayNodeLayout->invalidate(); } - (void)__setNeedsLayout @@ -1450,6 +1458,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(__instanceLock__); CGRect bounds = _threadSafeBounds; + + NSLog(@"haha"); if (CGRectEqualToRect(bounds, CGRectZero)) { // Performing layout on a zero-bounds view often results in frame calculations @@ -1490,57 +1500,76 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return; } - if (_calculatedDisplayNodeLayout->isDirty() || - CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size) == NO) + // Check if we have to do a measure pass as the calculated layout cannot be reused + if (_calculatedDisplayNodeLayout->isDirty() == NO && CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { - [self cancelLayoutTransition]; - - BOOL didCreateNewContext = NO; - BOOL didOverrideExistingContext = NO; - BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); - ASLayoutElementContext context; - if (ASLayoutElementContextIsNull(ASLayoutElementGetCurrentContext())) { - context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); + return; + } + + // Calculated layout is not reusable try the pending layout + [self cancelLayoutTransition]; + + BOOL didCreateNewContext = NO; + BOOL didOverrideExistingContext = NO; + BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); + ASLayoutElementContext context; + if (ASLayoutElementContextIsNull(ASLayoutElementGetCurrentContext())) { + context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); + ASLayoutElementSetCurrentContext(context); + didCreateNewContext = YES; + } else { + context = ASLayoutElementGetCurrentContext(); + if (context.needsVisualizeNode != shouldVisualizeLayout) { + context.needsVisualizeNode = shouldVisualizeLayout; ASLayoutElementSetCurrentContext(context); - didCreateNewContext = YES; - } else { - context = ASLayoutElementGetCurrentContext(); - if (context.needsVisualizeNode != shouldVisualizeLayout) { - context.needsVisualizeNode = shouldVisualizeLayout; - ASLayoutElementSetCurrentContext(context); - didOverrideExistingContext = YES; - } + didOverrideExistingContext = YES; } + } + + // Prepare for layout transition + CGSize parentSize = bounds.size; + ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); + + if (CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + //constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - // Prepare for layout transition - CGSize parentSize = bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); - auto previousLayout = _calculatedDisplayNodeLayout; - auto pendingLayout = std::make_shared( - [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, - parentSize - ); - - if (didCreateNewContext) { - ASLayoutElementClearCurrentContext(); - } else if (didOverrideExistingContext) { - context.needsVisualizeNode = !context.needsVisualizeNode; - ASLayoutElementSetCurrentContext(context); + } + + // If we are the root node we use the + if (_supernode == nil) { + if (CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + constrainedSize = _pendingDisplayNodeLayout->constrainedSize; } - - ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendintLayout->layout should not be nil! %@", self); + //constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; - _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:pendingLayout - previousLayout:previousLayout]; + } + + + auto previousLayout = _calculatedDisplayNodeLayout; + auto pendingLayout = std::make_shared( + [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], + constrainedSize, + parentSize + ); + + if (didCreateNewContext) { + ASLayoutElementClearCurrentContext(); + } else if (didOverrideExistingContext) { + context.needsVisualizeNode = !context.needsVisualizeNode; + ASLayoutElementSetCurrentContext(context); + } - // Only complete the pending layout transition if the node is not a subnode of a node that is currently - // in a layout transition - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { - // Complete the pending layout transition immediately - [self _completePendingLayoutTransition]; - } + ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendintLayout->layout should not be nil! %@", self); + + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:pendingLayout + previousLayout:previousLayout]; + + // Only complete the pending layout transition if the node is not a subnode of a node that is currently + // in a layout transition + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { + // Complete the pending layout transition immediately + [self _completePendingLayoutTransition]; } } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index dc4f0eed81..c291cd391d 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -137,6 +137,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo int32_t _pendingTransitionID; ASLayoutTransition *_pendingLayoutTransition; std::shared_ptr _calculatedDisplayNodeLayout; + std::shared_ptr _pendingDisplayNodeLayout; ASDisplayNodeViewBlock _viewBlock; ASDisplayNodeLayerBlock _layerBlock; diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index 29d4bd4319..be0e74ccff 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -41,14 +41,15 @@ attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; textNode.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10); - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))]; - textNode.frame = CGRectMake(50, 50, textNode.calculatedSize.width, textNode.calculatedSize.height); + + ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))]; + textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height); + + textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); [backgroundView addSubview:textNode.view]; backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); - textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); - [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; ASSnapshotVerifyLayer(backgroundView.layer, nil); } From c7765f6183974e9c872d9315e25faf81039bc266 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 14 Nov 2016 20:29:50 -0800 Subject: [PATCH 15/39] Add pending layout to ASDisplayNode --- AsyncDisplayKit/ASDisplayNode.mm | 55 +++++++++---------- .../ASDisplayNodeImplicitHierarchyTests.m | 4 +- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b3b8081fb7..71efecff5b 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -315,7 +315,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _environmentState = ASEnvironmentStateMakeDefault(); _calculatedDisplayNodeLayout = std::make_shared(); - _pendingDisplayNodeLayout = std::make_shared(); + _pendingDisplayNodeLayout = nullptr; _defaultLayoutTransitionDuration = 0.2; _defaultLayoutTransitionDelay = 0.0; @@ -786,11 +786,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } - + + // Creat a pending display node layout for the layout pass _pendingDisplayNodeLayout = std::make_shared( - [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, - parentSize + [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], + constrainedSize, + parentSize ); ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); return _pendingDisplayNodeLayout->layout; @@ -1403,7 +1404,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // This will cause the next layout pass to compute a new layout instead of returning // the cached layout in case the constrained or parent size did not change _calculatedDisplayNodeLayout->invalidate(); - _pendingDisplayNodeLayout->invalidate(); + if (_pendingDisplayNodeLayout != nullptr) { + _pendingDisplayNodeLayout->invalidate(); + } } - (void)__setNeedsLayout @@ -1469,6 +1472,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } [self measureNodeWithBoundsIfNecessary:bounds]; + _pendingDisplayNodeLayout = nullptr; // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer // was created @@ -1527,30 +1531,23 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // Prepare for layout transition - CGSize parentSize = bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); - - if (CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { - //constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - - } - - // If we are the root node we use the - if (_supernode == nil) { - if (CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { - constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - } - //constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; - - } - - auto previousLayout = _calculatedDisplayNodeLayout; - auto pendingLayout = std::make_shared( - [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, - parentSize - ); + auto pendingLayout = [=]() -> std::shared_ptr { + // Check if the pending display node layout can be used + if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + return _pendingDisplayNodeLayout; + } + + // Pending layout cannot be used let's use the frame size to calculate the pending layout + CGSize parentSize = bounds.size; + ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); + + return std::make_shared( + [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], + constrainedSize, + parentSize + ); + }(); if (didCreateNewContext) { ASLayoutElementClearCurrentContext(); diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 9c5ee21c1d..a8eaa2cadc 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -132,8 +132,8 @@ XCTAssertEqual(node.subnodes[1], node2); node.layoutState = @2; - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); - [node layoutIfNeeded]; + [node setNeedsLayout]; // After a state change the layout needs to be invalidated + [node layoutIfNeeded]; // A new layout pass will trigger the hiearchy transition XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node3); From 2744998a10f2be347b3d5043e4d459c7af611a23 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 15 Nov 2016 17:02:39 -0800 Subject: [PATCH 16/39] Further progress --- AsyncDisplayKit/ASButtonNode.mm | 6 +-- AsyncDisplayKit/ASCellNode.mm | 4 +- AsyncDisplayKit/ASDisplayNode.mm | 68 ++++++++++++++++++------ AsyncDisplayKit/ASTextNode.mm | 6 +-- examples/ASDKgram/Sample/CommentsNode.m | 2 +- examples/ASDKgram/Sample/PhotoCellNode.m | 4 +- 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index bdd8589bf6..06cc6c9aae 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -142,7 +142,7 @@ [self updateBackgroundImage]; [self updateImage]; [self updateTitle]; - [self invalidateSize]; + [self setNeedsLayout]; } - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously @@ -235,7 +235,7 @@ return; _contentSpacing = contentSpacing; - [self invalidateSize]; + [self setNeedsLayout]; } - (BOOL)laysOutHorizontally @@ -251,7 +251,7 @@ return; _laysOutHorizontally = laysOutHorizontally; - [self invalidateSize]; + [self setNeedsLayout]; } - (ASVerticalAlignment)contentVerticalAlignment diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index dd7636fa09..030d8f7520 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -118,10 +118,8 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init _viewControllerNode.frame = self.bounds; } -- (void)didInvalidateSize +- (void)displayNodeDidInvalidateSizeOldSize:(CGSize)oldSize { - CGSize oldSize = self.calculatedSize; - ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)); if (_interactionDelegate != nil) { constrainedSize = [_interactionDelegate constrainedSizeForNode:self]; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 71efecff5b..b24a28775f 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -713,9 +713,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Mark the node for layout in the next layout pass [self invalidateCalculatedLayout]; - // Hook for subclasses to get size invalidation changes - [self didInvalidateSize]; - if (_supernode) { ASDisplayNode *supernode = _supernode; __instanceLock__.unlock(); @@ -725,15 +722,35 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return; } - // We are now at the root node trigger a layout pass - [self setNeedsLayout]; + // Calculate a new pending layout. It will be applied in the next layout pass + ASLayout *layout = [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; + if (CGSizeEqualToSize(self.bounds.size, layout.size) == NO) { + // If the size of the layout changes inform the node of this + [self displayNodeDidInvalidateSizeOldSize:self.bounds.size]; + } + __instanceLock__.unlock(); } -- (void)didInvalidateSize +- (void)displayNodeDidInvalidateSizeOldSize:(CGSize)size { + // The default implementation of display node changes the size of itself to the new size + CGRect oldBounds = self.bounds; + CGSize oldSize = oldBounds.size; + CGSize newSize = _calculatedDisplayNodeLayout->layout.size; + + if (! CGSizeEqualToSize(oldSize, newSize)) { + self.bounds = (CGRect){ oldBounds.origin, newSize }; + // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint + // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. + CGPoint anchorPoint = self.anchorPoint; + CGPoint oldPosition = self.position; + CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; + CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; + self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); + } } - (void)sizeToFit @@ -1462,8 +1479,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDN::MutexLocker l(__instanceLock__); CGRect bounds = _threadSafeBounds; - NSLog(@"haha"); - if (CGRectEqualToRect(bounds, CGRectZero)) { // Performing layout on a zero-bounds view often results in frame calculations // with negative sizes after applying margins, which will cause @@ -1503,14 +1518,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); return; } + - // Check if we have to do a measure pass as the calculated layout cannot be reused - if (_calculatedDisplayNodeLayout->isDirty() == NO && CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) + // Check if we can reuse the calculated display node layout + if (_pendingDisplayNodeLayout == nullptr && + _calculatedDisplayNodeLayout->isDirty() == NO && + CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { + // Reuse calculatedDisplayNodeLayout for layout pass return; } - // Calculated layout is not reusable try the pending layout + // Calcualted layout is not reusable we need to transform to a new one [self cancelLayoutTransition]; BOOL didCreateNewContext = NO; @@ -1529,25 +1548,39 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) didOverrideExistingContext = YES; } } - - // Prepare for layout transition + + // Figure out previos and pending layout for layout transition auto previousLayout = _calculatedDisplayNodeLayout; auto pendingLayout = [=]() -> std::shared_ptr { - // Check if the pending display node layout can be used - if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + // Check if the pending display node layout can be used to transition to + if (_pendingDisplayNodeLayout != nullptr && + _pendingDisplayNodeLayout->isDirty() == NO && + CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { return _pendingDisplayNodeLayout; } - // Pending layout cannot be used let's use the frame size to calculate the pending layout CGSize parentSize = bounds.size; ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); + // Checkout if constrained size of layouts can be reused + if (_pendingDisplayNodeLayout != nullptr) { + constrainedSize = _pendingDisplayNodeLayout->constrainedSize; + } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO) { + constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + } + return std::make_shared( [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], constrainedSize, parentSize ); }(); + + // If the size of the new layout we wan to apply did change from the current bounds invalidate the whole tree up + // so the root node can resize in case it needs to be + if (CGSizeEqualToSize(self.bounds.size, pendingLayout->layout.size) == NO) { + [self invalidateSize]; + } if (didCreateNewContext) { ASLayoutElementClearCurrentContext(); @@ -1555,7 +1588,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) context.needsVisualizeNode = !context.needsVisualizeNode; ASLayoutElementSetCurrentContext(context); } - + + // Finally transition to pendingLayout ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendintLayout->layout should not be nil! %@", self); _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 897088149e..b329c405b8 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -330,7 +330,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset); if (needsUpdate) { _textContainerInset = textContainerInset; - [self invalidateSize]; + [self setNeedsLayout]; } } @@ -486,7 +486,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } // Tell the display node superclasses that the cached layout is incorrect now - [self invalidateSize]; + [self setNeedsLayout]; // Force display to create renderer with new size and redisplay with new string [self setNeedsDisplay]; @@ -509,7 +509,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _exclusionPaths = [exclusionPaths copy]; [self _invalidateRenderer]; - [self invalidateSize]; + [self setNeedsLayout]; [self setNeedsDisplay]; } diff --git a/examples/ASDKgram/Sample/CommentsNode.m b/examples/ASDKgram/Sample/CommentsNode.m index 29cea828e3..67859bd37f 100644 --- a/examples/ASDKgram/Sample/CommentsNode.m +++ b/examples/ASDKgram/Sample/CommentsNode.m @@ -79,7 +79,7 @@ labelsIndex++; } - [self invalidateSize]; + [self setNeedsLayout]; } } diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index 89bd91e5ac..176cf4b608 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -81,7 +81,7 @@ // where as local variable will never change if (locationModel == _photoModel.location) { _photoLocationLabel.attributedText = [photo locationAttributedStringWithFontSize:FONT_SIZE]; - [self invalidateSize]; + [self setNeedsLayout]; } }]; @@ -224,7 +224,7 @@ if (photo.commentFeed.numberOfItemsInFeed > 0) { [_photoCommentsView updateWithCommentFeedModel:photo.commentFeed]; - [self invalidateSize]; + [self setNeedsLayout]; } } From a71a1d85190804dac3773593a030bedb74c5955f Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 10:07:58 -0800 Subject: [PATCH 17/39] Address comments --- AsyncDisplayKit/ASCellNode.mm | 5 +- AsyncDisplayKit/ASCollectionView.mm | 4 +- AsyncDisplayKit/ASDisplayNode.h | 17 +-- AsyncDisplayKit/ASDisplayNode.mm | 135 +++++++++--------- AsyncDisplayKit/ASTableView.mm | 1 - AsyncDisplayKit/Details/_ASDisplayView.mm | 6 +- .../Private/ASDisplayNodeInternal.h | 32 +++-- .../ASDisplayNodeTestsHelper.m | 2 +- .../ASImageNodeSnapshotTests.m | 3 - .../ASTextNodeSnapshotTests.m | 3 +- 10 files changed, 103 insertions(+), 105 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 030d8f7520..7e21f581bb 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -120,10 +120,7 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init - (void)displayNodeDidInvalidateSizeOldSize:(CGSize)oldSize { - ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)); - if (_interactionDelegate != nil) { - constrainedSize = [_interactionDelegate constrainedSizeForNode:self]; - } + ASSizeRange constrainedSize = [_interactionDelegate constrainedSizeForNode:self]; CGSize newSize = [self layoutThatFits:constrainedSize].size; if (CGSizeEqualToSize(oldSize, newSize) == NO) { diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 81132202d7..08dabb9678 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -765,9 +765,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { - ASDisplayNode *node = [self nodeForItemAtIndexPath:indexPath]; - [node layoutIfNeeded]; - return node.calculatedSize; + return [[self nodeForItemAtIndexPath:indexPath] calculatedSize]; } - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index d6ecce6229..1798717ac2 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -256,9 +256,6 @@ extern NSInteger const ASDefaultDrawingPriority; // TODO: coalesc: Documentation - (void)invalidateSize; -- (void)didInvalidateSize; -- (void)sizeToFit; -- (CGSize)sizeThatFits:(CGSize)size; /** * @abstract Asks the node to return a layout based on given size range. @@ -637,21 +634,21 @@ extern NSInteger const ASDefaultDrawingPriority; @interface ASDisplayNode (UIViewBridge) /** - * Marks the view as needing display. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + * Marks the view as needing display. Convenience for use whether the view / layer is loaded or not. Safe to call + * from a background thread. */ - (void)setNeedsDisplay; /** * Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. - * - * If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated, - * and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size. - * - * Note: ASCellNode has special behavior in that calling this method will automatically notify - * the containing ASTableView / ASCollectionView that the cell should be resized, if necessary. */ - (void)setNeedsLayout; +/** + * Recalculate the receiver’s layout, if required. + * + * When this message is received, the layer’s super layers are traversed until a ancestor layer is found that does not require layout. Then layout is performed on the entire layer-tree beneath that ancestor. + */ - (void)layoutIfNeeded; @property (nonatomic, strong, nullable) id contents; // default=nil diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index b24a28775f..173438dfea 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -713,8 +713,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Mark the node for layout in the next layout pass [self invalidateCalculatedLayout]; - if (_supernode) { - ASDisplayNode *supernode = _supernode; + ASDisplayNode *supernode = _supernode; + if (supernode) { __instanceLock__.unlock(); // Cause supernode's layout to be invalidated // We need to release the lock to prevent a deadlock @@ -722,50 +722,39 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return; } - // Calculate a new pending layout. It will be applied in the next layout pass - ASLayout *layout = [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; - if (CGSizeEqualToSize(self.bounds.size, layout.size) == NO) { - // If the size of the layout changes inform the node of this - [self displayNodeDidInvalidateSizeOldSize:self.bounds.size]; - } + // We are the root node and need to re-flow the layout; one of our children requested to have its size re-set. + CGSize boundsSize = self.bounds.size; + // Figure out constrainedSize to use + ASLayout *layout = nil; + if (_pendingDisplayNodeLayout != nullptr) { + layout = [self layoutThatFits:_pendingDisplayNodeLayout->constrainedSize]; + } else { + layout = [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; + } + if (CGSizeEqualToSize(boundsSize, layout.size) == NO) { + // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) + // that we need it to change our bounds size. + [self displayNodeDidInvalidateSizeOldSize:boundsSize]; + } __instanceLock__.unlock(); } +// TODO: Pass in oldSize and new layout - (void)displayNodeDidInvalidateSizeOldSize:(CGSize)size -{ - // The default implementation of display node changes the size of itself to the new size - CGRect oldBounds = self.bounds; - CGSize oldSize = oldBounds.size; - CGSize newSize = _calculatedDisplayNodeLayout->layout.size; - - if (! CGSizeEqualToSize(oldSize, newSize)) { - self.bounds = (CGRect){ oldBounds.origin, newSize }; - - // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint - // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - CGPoint anchorPoint = self.anchorPoint; - CGPoint oldPosition = self.position; - CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; - CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - } -} - -- (void)sizeToFit { ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); - - [self setNeedsLayout]; - - CGSize maxSize = _supernode ? _supernode.bounds.size : CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); - CGSize newSize = [self sizeThatFits:maxSize]; - + // The default implementation of display node changes the size of itself to the new size CGRect oldBounds = self.bounds; CGSize oldSize = oldBounds.size; + CGSize newSize = CGSizeZero; + if (_pendingDisplayNodeLayout != nullptr) { + newSize = _pendingDisplayNodeLayout->layout.size; + } else { + newSize = _calculatedDisplayNodeLayout->layout.size; + } if (! CGSizeEqualToSize(oldSize, newSize)) { self.bounds = (CGRect){ oldBounds.origin, newSize }; @@ -778,13 +767,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); } - - __instanceLock__.unlock(); -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, size)].size; } - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize @@ -1418,6 +1400,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)invalidateCalculatedLayout { + ASDN::MutexLocker l(__instanceLock__); + // This will cause the next layout pass to compute a new layout instead of returning // the cached layout in case the constrained or parent size did not change _calculatedDisplayNodeLayout->invalidate(); @@ -1448,17 +1432,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) */ - (void)__layoutIfNeeded { - ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - if ([supernode __needsLayout]) { - [supernode __layoutIfNeeded]; - } else { - // Layout all subviews starting from the first node that needs layout - [self __layoutSublayers]; - } + // TODO: Nothing in here yet } - (void)__setNeedsDisplay @@ -1520,18 +1494,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } - // Check if we can reuse the calculated display node layout - if (_pendingDisplayNodeLayout == nullptr && - _calculatedDisplayNodeLayout->isDirty() == NO && - CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) - { - // Reuse calculatedDisplayNodeLayout for layout pass - return; + // Check if we can reuse the calculated display node layout. We prefer the _pendingDisplayNodeLayout over the + // _calculatedDisplayNodeLayout though + if (_pendingDisplayNodeLayout == nullptr) { + if (_calculatedDisplayNodeLayout->isDirty() == NO && CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { + // Reuse calculatedDisplayNodeLayout for layout pass + return; + } } - - // Calcualted layout is not reusable we need to transform to a new one + + // calculatedDisplayNodeLayout is not reusable we need to transition to a new one [self cancelLayoutTransition]; - + BOOL didCreateNewContext = NO; BOOL didOverrideExistingContext = NO; BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); @@ -1559,14 +1533,22 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _pendingDisplayNodeLayout; } + // By default use the bounds CGSize parentSize = bounds.size; ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); // Checkout if constrained size of layouts can be reused - if (_pendingDisplayNodeLayout != nullptr) { + if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + // We assume the size for the last returned layoutThatFits: layout was applied use it's constrainedSizes constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO) { + } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && + CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { + // We assume the _calculatedDisplayNodeLayout is still valid constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + } else { + // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can + // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to + // the one returned from layoutThatFits: } return std::make_shared( @@ -1576,8 +1558,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ); }(); - // If the size of the new layout we wan to apply did change from the current bounds invalidate the whole tree up - // so the root node can resize in case it needs to be + // If the size of the new layout to apply did change from the current bounds, invalidate the whole tree up + // so the root node can handle a resizing if necessary if (CGSizeEqualToSize(self.bounds.size, pendingLayout->layout.size) == NO) { [self invalidateSize]; } @@ -1604,6 +1586,19 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } +- (ASSizeRange)conrainedSizeForCurrentLayout +{ + ASSizeRange constrainedSize = ASSizeRangeMake(self.bounds.size); + + // Checkout if constrained size of layouts can be reused + if (_pendingDisplayNodeLayout != nullptr) { + constrainedSize = _pendingDisplayNodeLayout->constrainedSize; + } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO) { + constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + } + return constrainedSize; +} + - (void)layoutDidFinish { // Hook for subclasses @@ -2691,12 +2686,18 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (CGSize)calculatedSize { ASDN::MutexLocker l(__instanceLock__); + if (_pendingDisplayNodeLayout != nullptr) { + return _pendingDisplayNodeLayout->layout.size; + } return _calculatedDisplayNodeLayout->layout.size; } - (ASSizeRange)constrainedSizeForCalculatedLayout { ASDN::MutexLocker l(__instanceLock__); + if (_pendingDisplayNodeLayout != nullptr) { + return _pendingDisplayNodeLayout->constrainedSize; + } return _calculatedDisplayNodeLayout->constrainedSize; } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 32ca49ce81..69ec54dc54 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -751,7 +751,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - [node layoutIfNeeded]; return node.calculatedSize.height; } diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 6b4e90963e..5742566538 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -16,6 +16,7 @@ #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" #import "ASObjectDescriptionHelpers.h" +#import "ASLayout.h" @interface _ASDisplayView () @property (nullable, atomic, weak, readwrite) ASDisplayNode *asyncdisplaykit_node; @@ -205,10 +206,7 @@ - (CGSize)sizeThatFits:(CGSize)size { ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - if (node) { - return [node sizeThatFits:size]; - } - return [super sizeThatFits:size]; + return node ? [node layoutThatFits:ASSizeRangeMake(size)].size : [super sizeThatFits:size]; } - (void)setNeedsDisplay diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index c291cd391d..49373f2805 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -189,12 +189,13 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo + (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node; -// The _ASDisplayLayer backing the node, if any. +/// The _ASDisplayLayer backing the node, if any. @property (nonatomic, readonly, strong) _ASDisplayLayer *asyncLayer; -// Bitmask to check which methods an object overrides. +/// Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; +/// Thread safe way to access the bounds of the node @property (nonatomic, assign) CGRect threadSafeBounds; @@ -202,23 +203,34 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (BOOL)__shouldLoadViewOrLayer; /** - Invoked before a call to setNeedsLayout to the underlying view + * Invoked before a call to setNeedsLayout to the underlying view */ - (void)__setNeedsLayout; +/** + * The node's supernodes are traversed until a ancestor node is found that does not require layout. Then layout + * is performed on the entire node-tree beneath that ancestor + */ - (void)__layoutIfNeeded; /** - Invoked after a call to setNeedsDisplay to the underlying view + * Invoked after a call to setNeedsDisplay to the underlying view */ - (void)__setNeedsDisplay; +/** + * Called from [CALayer layoutSublayers:]. Executes the layout pass for the node + */ - (void)__layoutSublayers; + +/* + * Internal method to set the supernode + */ - (void)__setSupernode:(ASDisplayNode *)supernode; /** - Internal method to add / replace / insert subnode and remove from supernode without checking if - node has automaticallyManagesSubnodes set to YES. + * Internal method to add / replace / insert subnode and remove from supernode without checking if + * node has automaticallyManagesSubnodes set to YES. */ - (void)_addSubnode:(ASDisplayNode *)subnode; - (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode; @@ -233,16 +245,16 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)__incrementVisibilityNotificationsDisabled; - (void)__decrementVisibilityNotificationsDisabled; -// Helper method to summarize whether or not the node run through the display process +/// Helper method to summarize whether or not the node run through the display process - (BOOL)__implementsDisplay; -// Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. +/// Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. - (void)displayImmediately; -// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. +/// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. - (instancetype)initWithViewClass:(Class)viewClass; -// Alternative initialiser for backing with a custom layer class. Supports asynchronous display with _ASDisplayLayer subclasses. +/// Alternative initialiser for backing with a custom layer class. Supports asynchronous display with _ASDisplayLayer subclasses. - (instancetype)initWithLayerClass:(Class)layerClass; @property (nonatomic, assign) CGFloat contentsScaleForDisplay; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m index 70f9502ac8..0a64095770 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m @@ -45,7 +45,7 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block) } void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size) { - CGSize sizeThatFits = [node sizeThatFits:size]; + CGSize sizeThatFits = [node layoutThatFits:ASSizeRangeMake(size)].size; node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; } void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) { diff --git a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m index ac53e3cd4e..9ebb36ce30 100644 --- a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m @@ -47,14 +47,11 @@ imageNode.style.width = ASDimensionMake(forcedImageSize.width); imageNode.style.height = ASDimensionMake(forcedImageSize.height); ASDisplayNodeSizeToFitSize(imageNode, forcedImageSize); - [imageNode layoutIfNeeded]; ASSnapshotVerifyNode(imageNode, @"first"); imageNode.style.width = ASDimensionMake(200); imageNode.style.height = ASDimensionMake(200); ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(200, 200)); - [imageNode layoutIfNeeded]; - ASSnapshotVerifyNode(imageNode, @"second"); XCTAssert(CGImageGetWidth((CGImageRef)imageNode.contents) == forcedImageSize.width * imageNode.contentsScale && diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index be0e74ccff..88f6bca43e 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -25,8 +25,7 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); - ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; - textNode.frame = CGRectMake(0, 0, layout.size.width, layout.size.height); + ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); ASSnapshotVerifyNode(textNode, nil); } From c14a86c4d6975094b4e7303f549dd0030a3393dc Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 11:02:04 -0800 Subject: [PATCH 18/39] More improvements to figure out the constrained size that should be used in the layout pass --- AsyncDisplayKit/ASDisplayNode.mm | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 173438dfea..fa24fd730b 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -711,7 +711,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) __instanceLock__.lock(); // Mark the node for layout in the next layout pass - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; ASDisplayNode *supernode = _supernode; if (supernode) { @@ -1417,15 +1417,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self invalidateCalculatedLayout]; } -/** - * Returns a BOOL indicating whether the layer has been marked as needing a layout update. - */ -- (BOOL)__needsLayout -{ - ASDN::MutexLocker l(__instanceLock__); - return _calculatedDisplayNodeLayout->isDirty(); -} - /** * The node's supernodes are traversed until a ancestor node is found that does not require layout. Then layout * is performed on the entire node-tree beneath that ancestor @@ -1530,25 +1521,31 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (_pendingDisplayNodeLayout != nullptr && _pendingDisplayNodeLayout->isDirty() == NO && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + // We assume the _pendingDisplayNodeLayout was created by layoutThatFits: to set the size of the node + // now it's time to apply it and to become the _calculatedDisplayNodeLayout return _pendingDisplayNodeLayout; } - // By default use the bounds + // The _pendingDisplayNodeLayout can not be used to be applied we need to figure out we need to calculate a new + // layout based on a constrainedSize + + // Use as default constrained size the bounds CGSize parentSize = bounds.size; ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); - // Checkout if constrained size of layouts can be reused + // Checkout if constrained size of pending or calculated display node layout can be used if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { - // We assume the size for the last returned layoutThatFits: layout was applied use it's constrainedSizes + // We assume the size from the last returned layoutThatFits: layout was applied so use the pejnding display node + // layout constrained size constrainedSize = _pendingDisplayNodeLayout->constrainedSize; } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { - // We assume the _calculatedDisplayNodeLayout is still valid + // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; } else { // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to - // the one returned from layoutThatFits: + // the one returned from layoutThatFits: or layoutThatFits: was never called } return std::make_shared( From 257d4c3a1f10ee164d5c1c9f4622edf6f3d643bb Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 11:44:13 -0800 Subject: [PATCH 19/39] Further improvements and fixing tests --- AsyncDisplayKit/ASDisplayNode.mm | 5 +++-- .../ASDisplayNodeImplicitHierarchyTests.m | 10 +++++----- AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m | 2 +- AsyncDisplayKitTests/ASTextNodeSnapshotTests.m | 8 ++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index fa24fd730b..bdd32cec0c 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -741,7 +741,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) __instanceLock__.unlock(); } -// TODO: Pass in oldSize and new layout +// TODO: coalesc: Pass in oldSize and new layout - (void)displayNodeDidInvalidateSizeOldSize:(CGSize)size { ASDisplayNodeAssertThreadAffinity(self); @@ -1423,7 +1423,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) */ - (void)__layoutIfNeeded { - // TODO: Nothing in here yet + // TODO: calesc: What should happen in here? Especially if we layoutIfNeeded is called and the view / layer is not created yet + [self __layoutSublayers]; } - (void)__setNeedsDisplay diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index a8eaa2cadc..d88e0d24ff 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -90,7 +90,7 @@ return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, node5]]; }; - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); @@ -126,7 +126,7 @@ } }; - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); @@ -199,7 +199,7 @@ dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Measurement happens in the background - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); // Dispatch back to the main thread to let the insertion / deletion of subnodes happening dispatch_async(dispatch_get_main_queue(), ^{ @@ -214,7 +214,7 @@ node.layoutState = @2; [node setNeedsLayout]; - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); // Dispatch back to the main thread to let the insertion / deletion of subnodes happening dispatch_async(dispatch_get_main_queue(), ^{ @@ -261,7 +261,7 @@ XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); diff --git a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m index 763237003d..203a46b94d 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m @@ -29,7 +29,7 @@ return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5, 5, 5, 5) child:subnode]; }; - ASDisplayNodeSizeToFitSize(node, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); ASSnapshotVerifyNode(node, nil); } diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index 88f6bca43e..2a2a1b704d 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -43,11 +43,11 @@ ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))]; textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height); - - textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); [backgroundView addSubview:textNode.view]; backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); + + textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; ASSnapshotVerifyLayer(backgroundView.layer, nil); @@ -90,7 +90,7 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }]; // Set exclusion paths to trigger slow path textNode.exclusionPaths = @[ [UIBezierPath bezierPath] ]; - ASDisplayNodeSizeToFitSize(textNode, CGSizeMake(100, 50)); + ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))); ASSnapshotVerifyNode(textNode, nil); } @@ -102,7 +102,7 @@ textNode.shadowOpacity = 0.3; textNode.shadowRadius = 3; textNode.shadowOffset = CGSizeMake(0, 1); - ASDisplayNodeSizeToFitSize(textNode, CGSizeMake(INFINITY, INFINITY)); + ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); ASSnapshotVerifyNode(textNode, nil); } From 30b76444156ba72981a4af22987e3cd0156d1ec5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 12:57:02 -0800 Subject: [PATCH 20/39] Factor out constrainedSize usage for layout pass --- AsyncDisplayKit/ASDisplayNode.mm | 47 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index bdd32cec0c..eb7838d977 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -724,14 +724,16 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // We are the root node and need to re-flow the layout; one of our children requested to have its size re-set. CGSize boundsSize = self.bounds.size; + ASSizeRange constrainedSize = [self constrainedSizeForLayoutPass]; + ASLayout *layout = [self layoutThatFits:constrainedSize]; // Figure out constrainedSize to use - ASLayout *layout = nil; + /*ASLayout *layout = nil; if (_pendingDisplayNodeLayout != nullptr) { layout = [self layoutThatFits:_pendingDisplayNodeLayout->constrainedSize]; } else { layout = [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; - } + }*/ if (CGSizeEqualToSize(boundsSize, layout.size) == NO) { // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) // that we need it to change our bounds size. @@ -1532,26 +1534,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Use as default constrained size the bounds CGSize parentSize = bounds.size; - ASSizeRange constrainedSize = ASSizeRangeMake(parentSize); - - // Checkout if constrained size of pending or calculated display node layout can be used - if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { - // We assume the size from the last returned layoutThatFits: layout was applied so use the pejnding display node - // layout constrained size - constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && - CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { - // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different - constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; - } else { - // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can - // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to - // the one returned from layoutThatFits: or layoutThatFits: was never called - } + ASSizeRange constrainedSize = [self constrainedSizeForLayoutPass]; return std::make_shared( [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, + [self constrainedSizeForLayoutPass], parentSize ); }(); @@ -1584,15 +1571,25 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } -- (ASSizeRange)conrainedSizeForCurrentLayout +- (ASSizeRange)constrainedSizeForLayoutPass { - ASSizeRange constrainedSize = ASSizeRangeMake(self.bounds.size); - - // Checkout if constrained size of layouts can be reused - if (_pendingDisplayNodeLayout != nullptr) { + // Use as default constrained size the bounds + CGRect bounds = self.threadSafeBounds; + ASSizeRange constrainedSize = ASSizeRangeMake(bounds.size); + + // Checkout if constrained size of pending or calculated display node layout can be used + if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + // We assume the size from the last returned layoutThatFits: layout was applied so use the pejnding display node + // layout constrained size constrainedSize = _pendingDisplayNodeLayout->constrainedSize; - } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO) { + } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && + CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { + // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + } else { + // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can + // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to + // the one returned from layoutThatFits: or layoutThatFits: was never called } return constrainedSize; } From 3d37e55c7b8bf0f49951d129f55b0fac2d760e22 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 14:11:22 -0800 Subject: [PATCH 21/39] Some more improvements around constrained size --- AsyncDisplayKit/ASDisplayNode.mm | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index eb7838d977..7a76802ea9 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -723,21 +723,22 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // We are the root node and need to re-flow the layout; one of our children requested to have its size re-set. - CGSize boundsSize = self.bounds.size; - ASSizeRange constrainedSize = [self constrainedSizeForLayoutPass]; - ASLayout *layout = [self layoutThatFits:constrainedSize]; - + CGSize oldSize = self.bounds.size; + // Figure out constrainedSize to use - /*ASLayout *layout = nil; + ASSizeRange constrainedSize = ASSizeRangeMake(self.bounds.size); if (_pendingDisplayNodeLayout != nullptr) { - layout = [self layoutThatFits:_pendingDisplayNodeLayout->constrainedSize]; - } else { - layout = [self layoutThatFits:_calculatedDisplayNodeLayout->constrainedSize]; - }*/ - if (CGSizeEqualToSize(boundsSize, layout.size) == NO) { + constrainedSize = _pendingDisplayNodeLayout->constrainedSize; + } else if (_calculatedDisplayNodeLayout->layout != nil) { + constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + } + + // Check if the returned layout has a different size as the current bounds + ASLayout *layout = [self layoutThatFits:constrainedSize]; + if (CGSizeEqualToSize(oldSize, layout.size) == NO) { // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) // that we need it to change our bounds size. - [self displayNodeDidInvalidateSizeOldSize:boundsSize]; + [self displayNodeDidInvalidateSizeOldSize:oldSize]; } __instanceLock__.unlock(); @@ -1538,7 +1539,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return std::make_shared( [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - [self constrainedSizeForLayoutPass], + constrainedSize, parentSize ); }(); From cc297a2dae2cf92a9b50ed1e0ab6d6fc2969760a Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 16:00:03 -0800 Subject: [PATCH 22/39] Fix some tests and add a workaround to ceil the values from self.bounds.size before checking for equality --- AsyncDisplayKit/ASDisplayNode.mm | 9 ++------- .../ASDisplayNodeImplicitHierarchyTests.m | 4 ++++ AsyncDisplayKitTests/ASDisplayNodeTests.m | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 7a76802ea9..85d2e4ecfa 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1420,14 +1420,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self invalidateCalculatedLayout]; } -/** - * The node's supernodes are traversed until a ancestor node is found that does not require layout. Then layout - * is performed on the entire node-tree beneath that ancestor - */ - (void)__layoutIfNeeded { - // TODO: calesc: What should happen in here? Especially if we layoutIfNeeded is called and the view / layer is not created yet - [self __layoutSublayers]; + // For now this is a no op. } - (void)__setNeedsDisplay @@ -1546,7 +1541,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // If the size of the new layout to apply did change from the current bounds, invalidate the whole tree up // so the root node can handle a resizing if necessary - if (CGSizeEqualToSize(self.bounds.size, pendingLayout->layout.size) == NO) { + if (CGSizeEqualToSize(CGSizeMake(ASCeilPixelValue(self.bounds.size.width), ASCeilPixelValue(self.bounds.size.height)), pendingLayout->layout.size) == NO) { [self invalidateSize]; } diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index d88e0d24ff..6d32856497 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -91,6 +91,7 @@ }; ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); + [node view]; [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); @@ -127,6 +128,7 @@ }; ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); + [node view]; [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); @@ -192,6 +194,7 @@ }; // Intentionally trigger view creation + [node view]; [node2 view]; XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout also if one node is already loaded"]; @@ -205,6 +208,7 @@ dispatch_async(dispatch_get_main_queue(), ^{ // Layout on main + [node setNeedsLayout]; [node layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index c68e699381..b1f7ec7a2a 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -2168,6 +2168,7 @@ static bool stringContainsPointer(NSString *description, id p) { }; ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); + [node view]; [node layoutIfNeeded]; NSInteger underlayIndex = [node.subnodes indexOfObjectIdenticalTo:underlay]; @@ -2189,6 +2190,7 @@ static bool stringContainsPointer(NSString *description, id p) { }; ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); + [node view]; [node layoutIfNeeded]; NSInteger underlayIndex = [node.subnodes indexOfObjectIdenticalTo:underlay]; From 8964ce86b6c7a9eaef76fad909d91808112ae198 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 16:22:25 -0800 Subject: [PATCH 23/39] Revert ASTextNode workaround for side effect in calculateSizeThatFits: --- AsyncDisplayKit/ASTextNode.mm | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index b329c405b8..242046f903 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -237,19 +237,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return [self _rendererWithBounds:self.threadSafeBounds]; } -- (ASTextKitRenderer *)_rendererWithBoundsSlow:(CGRect)bounds -{ - ASDN::MutexLocker l(__instanceLock__); - - CGSize constrainedSize = bounds.size; - if (_constrainedSize.width == -INFINITY) { - constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); - constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); - } - return [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] - constrainedSize:constrainedSize]; -} - - (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds { ASDN::MutexLocker l(__instanceLock__); @@ -416,7 +403,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [self setNeedsDisplay]; - CGSize size = [self _rendererWithBoundsSlow:{.size=constrainedSize}].size; + CGSize size = [self _renderer].size; if (_attributedText.length > 0) { self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; @@ -486,7 +473,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } // Tell the display node superclasses that the cached layout is incorrect now - [self setNeedsLayout]; + [self invalidateCalculatedLayout]; // Force display to create renderer with new size and redisplay with new string [self setNeedsDisplay]; @@ -509,7 +496,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _exclusionPaths = [exclusionPaths copy]; [self _invalidateRenderer]; - [self setNeedsLayout]; + [self invalidateCalculatedLayout]; [self setNeedsDisplay]; } @@ -549,7 +536,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top); - ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:drawParameterBounds]; + ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; // Fill background if (backgroundColor != nil) { From c29b7a184c1a99c3457ae386b770a39e30759fd0 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 16:38:28 -0800 Subject: [PATCH 24/39] Introduce ASFloorSizeValues and ASCeilSizeValues --- AsyncDisplayKit/ASDisplayNode.mm | 12 ++++++------ AsyncDisplayKit/Private/ASInternalHelpers.h | 4 ++++ AsyncDisplayKit/Private/ASInternalHelpers.m | 10 ++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 85d2e4ecfa..076cf407c9 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1482,12 +1482,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); return; } - // Check if we can reuse the calculated display node layout. We prefer the _pendingDisplayNodeLayout over the // _calculatedDisplayNodeLayout though if (_pendingDisplayNodeLayout == nullptr) { - if (_calculatedDisplayNodeLayout->isDirty() == NO && CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { + if (_calculatedDisplayNodeLayout->isDirty() == NO && + CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { // Reuse calculatedDisplayNodeLayout for layout pass return; } @@ -1519,7 +1519,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Check if the pending display node layout can be used to transition to if (_pendingDisplayNodeLayout != nullptr && _pendingDisplayNodeLayout->isDirty() == NO && - CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { // We assume the _pendingDisplayNodeLayout was created by layoutThatFits: to set the size of the node // now it's time to apply it and to become the _calculatedDisplayNodeLayout return _pendingDisplayNodeLayout; @@ -1541,7 +1541,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // If the size of the new layout to apply did change from the current bounds, invalidate the whole tree up // so the root node can handle a resizing if necessary - if (CGSizeEqualToSize(CGSizeMake(ASCeilPixelValue(self.bounds.size.width), ASCeilPixelValue(self.bounds.size.height)), pendingLayout->layout.size) == NO) { + if (CGSizeEqualToSize(ASCeilSizeValues(bounds.size), pendingLayout->layout.size) == NO) { [self invalidateSize]; } @@ -1574,12 +1574,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASSizeRange constrainedSize = ASSizeRangeMake(bounds.size); // Checkout if constrained size of pending or calculated display node layout can be used - if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, bounds.size)) { + if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { // We assume the size from the last returned layoutThatFits: layout was applied so use the pejnding display node // layout constrained size constrainedSize = _pendingDisplayNodeLayout->constrainedSize; } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && - CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, bounds.size)) { + CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; } else { diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index f145abac7c..7c565f1cbe 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -31,8 +31,12 @@ void ASPerformBackgroundDeallocation(id object); CGFloat ASScreenScale(); +CGSize ASFloorSizeValues(CGSize s); + CGFloat ASFloorPixelValue(CGFloat f); +CGSize ASCeilSizeValues(CGSize s); + CGFloat ASCeilPixelValue(CGFloat f); CGFloat ASRoundPixelValue(CGFloat f); diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.m b/AsyncDisplayKit/Private/ASInternalHelpers.m index c9dae927ef..04c9972b62 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.m +++ b/AsyncDisplayKit/Private/ASInternalHelpers.m @@ -95,12 +95,22 @@ CGFloat ASScreenScale() return __scale; } +CGSize ASFloorSizeValues(CGSize s) +{ + return CGSizeMake(ASFloorPixelValue(s.width), ASFloorPixelValue(s.height)); +} + CGFloat ASFloorPixelValue(CGFloat f) { CGFloat scale = ASScreenScale(); return floor(f * scale) / scale; } +CGSize ASCeilSizeValues(CGSize s) +{ + return CGSizeMake(ASCeilPixelValue(s.width), ASCeilPixelValue(s.height)); +} + CGFloat ASCeilPixelValue(CGFloat f) { CGFloat scale = ASScreenScale(); From d73712059da4794a30ef88413e1f820eb4d1ba35 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 20:08:37 -0800 Subject: [PATCH 25/39] Remove unecessary measurement pass in ASCellNode --- AsyncDisplayKit/ASCellNode.mm | 6 ++---- AsyncDisplayKit/ASDisplayNode.h | 10 +++++++--- AsyncDisplayKit/ASDisplayNode.mm | 11 +++-------- .../Private/ASDisplayNode+FrameworkPrivate.h | 8 ++++++++ 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 7e21f581bb..0b90180562 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -118,11 +118,9 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init _viewControllerNode.frame = self.bounds; } -- (void)displayNodeDidInvalidateSizeOldSize:(CGSize)oldSize +- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)newSize { - ASSizeRange constrainedSize = [_interactionDelegate constrainedSizeForNode:self]; - CGSize newSize = [self layoutThatFits:constrainedSize].size; - + CGSize oldSize = self.bounds.size; if (CGSizeEqualToSize(oldSize, newSize) == NO) { self.frame = {self.frame.origin, newSize}; [self didRelayoutFromOldSize:oldSize toNewSize:newSize]; diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 1798717ac2..c8795bb4a2 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -254,9 +254,6 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ -// TODO: coalesc: Documentation -- (void)invalidateSize; - /** * @abstract Asks the node to return a layout based on given size range. * @@ -309,6 +306,13 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout; +/** + * @abstract Sublcass hook for nodes that are acting as root nodes. This method get's called if one of the subnodes + * size is invalidated and may need to result in a different size as the current calculated size. + */ +- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)newSize; + + /** @name Managing the nodes hierarchy */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 076cf407c9..80d0aa5dbd 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -738,26 +738,21 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (CGSizeEqualToSize(oldSize, layout.size) == NO) { // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) // that we need it to change our bounds size. - [self displayNodeDidInvalidateSizeOldSize:oldSize]; + [self displayNodeDidInvalidateSizeNewSize:layout.size]; } __instanceLock__.unlock(); } // TODO: coalesc: Pass in oldSize and new layout -- (void)displayNodeDidInvalidateSizeOldSize:(CGSize)size +- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size { ASDisplayNodeAssertThreadAffinity(self); // The default implementation of display node changes the size of itself to the new size CGRect oldBounds = self.bounds; CGSize oldSize = oldBounds.size; - CGSize newSize = CGSizeZero; - if (_pendingDisplayNodeLayout != nullptr) { - newSize = _pendingDisplayNodeLayout->layout.size; - } else { - newSize = _calculatedDisplayNodeLayout->layout.size; - } + CGSize newSize = size; if (! CGSizeEqualToSize(oldSize, newSize)) { self.bounds = (CGRect){ oldBounds.origin, newSize }; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 620d976044..cdcd42d8d5 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -175,6 +175,14 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat */ - (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState; +/** + * @abstract Informs the root node that the intrinsic size of the receiver is no longer valid. + * + * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know + * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. + */ +- (void)invalidateSize; + @end @interface UIView (ASDisplayNodeInternal) From a88b2ae916d8a03f84a5b5bcf5cc8195889199e8 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 16 Nov 2016 20:29:05 -0800 Subject: [PATCH 26/39] Call setNeedsLayout in ASTextNode instead of just invalidate the calculated layout to trigger a relayout if necessary --- AsyncDisplayKit/ASTextNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 242046f903..657088f687 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -473,7 +473,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; } // Tell the display node superclasses that the cached layout is incorrect now - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; // Force display to create renderer with new size and redisplay with new string [self setNeedsDisplay]; From bd851cae9c357b451b8faa227baa589eb4e2479c Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 17 Nov 2016 08:58:22 -0800 Subject: [PATCH 27/39] Change some invalidateCalculatedLayout to setNeedsLayout calls --- AsyncDisplayKit/ASEditableTextNode.mm | 2 +- AsyncDisplayKit/ASTextNode.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index b47dc7ca14..4fd2dfdc8f 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -415,7 +415,7 @@ [_textKitComponents.textStorage setAttributedString:attributedStringToDisplay]; // Calculated size depends on the seeded text. - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; // Update if placeholder is shown. [self _updateDisplayingPlaceholder]; diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 657088f687..9eb503a13e 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -496,7 +496,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; _exclusionPaths = [exclusionPaths copy]; [self _invalidateRenderer]; - [self invalidateCalculatedLayout]; + [self setNeedsLayout]; [self setNeedsDisplay]; } From 2cd49642b9b5c6ec64954576bf57194f020751ac Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 17 Nov 2016 11:34:09 -0800 Subject: [PATCH 28/39] Address comments from @appleguy --- AsyncDisplayKit/ASButtonNode.mm | 1 - AsyncDisplayKit/ASCellNode+Internal.h | 6 ---- AsyncDisplayKit/ASCollectionView.mm | 5 --- AsyncDisplayKit/ASDisplayNode.h | 13 ------- AsyncDisplayKit/ASDisplayNode.mm | 35 ++++++++++--------- .../Details/UIView+ASConvenience.h | 1 - AsyncDisplayKit/Details/_ASDisplayLayer.mm | 8 +---- .../Private/ASDisplayNode+AsyncDisplay.mm | 2 +- .../Private/ASDisplayNode+FrameworkPrivate.h | 6 ++++ .../Private/ASDisplayNode+UIViewBridge.mm | 28 --------------- .../Private/ASDisplayNodeInternal.h | 8 +---- AsyncDisplayKit/Private/_ASPendingState.mm | 13 ------- .../ASDisplayNodeImplicitHierarchyTests.m | 15 ++++---- AsyncDisplayKitTests/ASDisplayNodeTests.m | 6 ++-- 14 files changed, 35 insertions(+), 112 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 06cc6c9aae..f260fd6a45 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -142,7 +142,6 @@ [self updateBackgroundImage]; [self updateImage]; [self updateTitle]; - [self setNeedsLayout]; } - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index d2ebd4e27c..99066aa123 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -29,12 +29,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged; -/// Returns the constrained size for the given node -- (ASSizeRange)constrainedSizeForNode:(ASCellNode *)node; - -// TODO: coalesc: Maybe we should push the relayout of nodes to the table / collection node? -//- (void)relayoutNodeIfNeecessary:(ASCellNode *)node; - /* * Methods to be called whenever the selection or highlight state changes * on ASCellNode. UIKit internally stores these values to update reusable cells. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 08dabb9678..cc870a9f79 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1577,11 +1577,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (ASSizeRange)constrainedSizeForNode:(ASCellNode *)node -{ - return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:[self indexPathForNode:node]]; -} - - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index c8795bb4a2..67f96f5c4e 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -306,12 +306,6 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout; -/** - * @abstract Sublcass hook for nodes that are acting as root nodes. This method get's called if one of the subnodes - * size is invalidated and may need to result in a different size as the current calculated size. - */ -- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)newSize; - /** @name Managing the nodes hierarchy */ @@ -648,13 +642,6 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)setNeedsLayout; -/** - * Recalculate the receiver’s layout, if required. - * - * When this message is received, the layer’s super layers are traversed until a ancestor layer is found that does not require layout. Then layout is performed on the entire layer-tree beneath that ancestor. - */ -- (void)layoutIfNeeded; - @property (nonatomic, strong, nullable) id contents; // default=nil @property (nonatomic, assign) BOOL clipsToBounds; // default==NO @property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 80d0aa5dbd..c729173491 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -744,7 +744,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) __instanceLock__.unlock(); } -// TODO: coalesc: Pass in oldSize and new layout - (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size { ASDisplayNodeAssertThreadAffinity(self); @@ -780,8 +779,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDN::MutexLocker l(__instanceLock__); if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) { - ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); - return _calculatedDisplayNodeLayout->layout ? : [ASLayout layoutWithLayoutElement:self size:{0, 0}]; + ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self); + return _calculatedDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } // Creat a pending display node layout for the layout pass @@ -790,8 +789,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) constrainedSize, parentSize ); - ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _layout should not be nil! %@", self); - return _pendingDisplayNodeLayout->layout; + + ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout->layout should not be nil! %@", self); + return _pendingDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } - (ASLayoutElementType)layoutElementType @@ -1432,7 +1432,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed. -- (void)__layoutSublayers +- (void)__layout { ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(__instanceLock__); @@ -1444,8 +1444,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // measureWithSizeRange: on subnodes to assert. return; } - - [self measureNodeWithBoundsIfNecessary:bounds]; + + // This method will confirm that the layout is up to date (and update if needed). + // Importantly, it will also APPLY the layout to all of our subnodes if there is not already another layout + // transition in progress. + [self _locked_measureNodeWithBoundsIfNecessary:bounds]; _pendingDisplayNodeLayout = nullptr; // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer @@ -1460,7 +1463,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } /// Needs to be called with lock held -- (void)measureNodeWithBoundsIfNecessary:(CGRect)bounds +- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds { // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of // the layout transition @@ -1488,7 +1491,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } - // calculatedDisplayNodeLayout is not reusable we need to transition to a new one + // _calculatedDisplayNodeLayout is not reusable we need to transition to a new one [self cancelLayoutTransition]; BOOL didCreateNewContext = NO; @@ -1548,7 +1551,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // Finally transition to pendingLayout - ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendintLayout->layout should not be nil! %@", self); + ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendingLayout->layout should not be nil! %@", self); _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self pendingLayout:pendingLayout @@ -1564,25 +1567,23 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASSizeRange)constrainedSizeForLayoutPass { - // Use as default constrained size the bounds CGRect bounds = self.threadSafeBounds; - ASSizeRange constrainedSize = ASSizeRangeMake(bounds.size); // Checkout if constrained size of pending or calculated display node layout can be used if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { - // We assume the size from the last returned layoutThatFits: layout was applied so use the pejnding display node + // We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node // layout constrained size - constrainedSize = _pendingDisplayNodeLayout->constrainedSize; + return _pendingDisplayNodeLayout->constrainedSize; } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different - constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; + return _calculatedDisplayNodeLayout->constrainedSize; } else { // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to // the one returned from layoutThatFits: or layoutThatFits: was never called + return ASSizeRangeMake(bounds.size); } - return constrainedSize; } - (void)layoutDidFinish diff --git a/AsyncDisplayKit/Details/UIView+ASConvenience.h b/AsyncDisplayKit/Details/UIView+ASConvenience.h index 9c918689ec..24a9021cfd 100644 --- a/AsyncDisplayKit/Details/UIView+ASConvenience.h +++ b/AsyncDisplayKit/Details/UIView+ASConvenience.h @@ -42,7 +42,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)setNeedsDisplay; - (void)setNeedsLayout; -- (void)layoutIfNeeded; @end diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 1a49396825..a1d74c15fd 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -122,12 +122,6 @@ ASDisplayNodeAssertMainThread(); [super setNeedsLayout]; } - -- (void)layoutIfNeeded -{ - ASDisplayNodeAssertMainThread(); - [super layoutIfNeeded]; -} #endif - (void)layoutSublayers @@ -135,7 +129,7 @@ ASDisplayNodeAssertMainThread(); [super layoutSublayers]; - [self.asyncdisplaykit_node __layoutSublayers]; + [self.asyncdisplaykit_node __layout]; } - (void)setNeedsDisplay diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index 4ee4ad0d3c..484b6b76a9 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -52,7 +52,7 @@ // if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { - [self __layoutSublayers]; + [self __layout]; } // Capture these outside the display block so they are retained. diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index cdcd42d8d5..cefa27e858 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -183,6 +183,12 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat */ - (void)invalidateSize; +/** + * @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes + * size is invalidated and may need to result in a different size as the current calculated size. + */ +- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)newSize; + @end @interface UIView (ASDisplayNodeInternal) diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index efa1c50d06..f4e67b7f9a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -354,34 +354,6 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo } } -- (void)layoutIfNeeded -{ - _bridge_prologue_write; - BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self); - if (shouldApply) { - // The node is loaded and we're on main. - // Quite the opposite of setNeedsDisplay, we must call __layoutIfNeeded before messaging - // the view or layer to ensure that measurement and implicitly added subnodes have been handled. - - // Calling __layoutIfNeeded while holding the property lock can cause deadlocks - _bridge_prologue_write_unlock; - [self __layoutIfNeeded]; - _bridge_prologue_write; - _messageToViewOrLayer(layoutIfNeeded); - } else if (__loaded(self)) { - // The node is loaded but we're not on main. - // We will call [self __setNeedsLayout] when we apply - // the pending state. We need to call it on main if the node is loaded - // to support automatic subnode management. - [ASDisplayNodeGetPendingState(self) layoutIfNeeded]; - } else { - // The node is not loaded and we're not on main. - _bridge_prologue_write_unlock; - [self __layoutIfNeeded]; - _bridge_prologue_write; - } -} - - (BOOL)isOpaque { _bridge_prologue_read; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 49373f2805..67bb2cd35c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -207,12 +207,6 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (void)__setNeedsLayout; -/** - * The node's supernodes are traversed until a ancestor node is found that does not require layout. Then layout - * is performed on the entire node-tree beneath that ancestor - */ -- (void)__layoutIfNeeded; - /** * Invoked after a call to setNeedsDisplay to the underlying view */ @@ -221,7 +215,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo /** * Called from [CALayer layoutSublayers:]. Executes the layout pass for the node */ -- (void)__layoutSublayers; +- (void)__layout; /* * Internal method to set the supernode diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index b9e1abcb46..f6c6685a19 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -24,7 +24,6 @@ typedef struct { // Properties int needsDisplay:1; int needsLayout:1; - int layoutIfNeeded:1; // Flags indicating that a given property should be applied to the view at creation int setClipsToBounds:1; @@ -273,11 +272,6 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; _flags.needsLayout = YES; } -- (void)layoutIfNeeded -{ - _flags.layoutIfNeeded = YES; -} - - (void)setClipsToBounds:(BOOL)flag { clipsToBounds = flag; @@ -769,9 +763,6 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [layer setNeedsLayout]; - - if (flags.layoutIfNeeded) - [layer layoutIfNeeded]; if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -900,9 +891,6 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [view setNeedsLayout]; - - if (flags.layoutIfNeeded) - [view layoutIfNeeded]; if (flags.setAsyncTransactionContainer) view.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; @@ -1117,7 +1105,6 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; || flags.setEdgeAntialiasingMask || flags.needsDisplay || flags.needsLayout - || flags.layoutIfNeeded || flags.setAsyncTransactionContainer || flags.setOpaque || flags.setIsAccessibilityElement diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 6d32856497..4208310282 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -91,8 +91,7 @@ }; ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); - [node view]; - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); @@ -128,14 +127,13 @@ }; ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); - [node view]; - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node2); node.layoutState = @2; [node setNeedsLayout]; // After a state change the layout needs to be invalidated - [node layoutIfNeeded]; // A new layout pass will trigger the hiearchy transition + [node.view layoutIfNeeded]; // A new layout pass will trigger the hiearchy transition XCTAssertEqual(node.subnodes[0], node1); XCTAssertEqual(node.subnodes[1], node3); @@ -209,7 +207,7 @@ // Layout on main [node setNeedsLayout]; - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -224,7 +222,7 @@ dispatch_async(dispatch_get_main_queue(), ^{ // Layout on main again - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node2); [expectation fulfill]; @@ -259,14 +257,13 @@ }; // Intentionally trigger view creation - [node view]; [node1 view]; [node2 view]; XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; XCTAssertEqual(node.subnodes[0], node1); node.layoutState = @2; diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index b1f7ec7a2a..fcfa42c138 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -2168,8 +2168,7 @@ static bool stringContainsPointer(NSString *description, id p) { }; ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); - [node view]; - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; NSInteger underlayIndex = [node.subnodes indexOfObjectIdenticalTo:underlay]; NSInteger overlayIndex = [node.subnodes indexOfObjectIdenticalTo:overlay]; @@ -2190,8 +2189,7 @@ static bool stringContainsPointer(NSString *description, id p) { }; ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); - [node view]; - [node layoutIfNeeded]; + [node.view layoutIfNeeded]; NSInteger underlayIndex = [node.subnodes indexOfObjectIdenticalTo:underlay]; NSInteger overlayIndex = [node.subnodes indexOfObjectIdenticalTo:overlay]; From 5d61b2b4e66cd3ff08a19a197045162397eac034 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 17 Nov 2016 17:00:23 -0800 Subject: [PATCH 29/39] Address further comments --- AsyncDisplayKit/ASDisplayNode.h | 10 ++++++--- AsyncDisplayKit/ASDisplayNode.mm | 21 ++++++++++--------- AsyncDisplayKit/ASTableView.mm | 5 ----- .../Private/ASDisplayNode+FrameworkPrivate.h | 2 +- .../Sample/ViewController.m | 1 - .../Sample/DetailViewController.m | 11 +++++----- .../Sample/SampleSizingNode.m | 2 +- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 67f96f5c4e..6e31d5154f 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -306,7 +306,6 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout; - /** @name Managing the nodes hierarchy */ @@ -632,13 +631,18 @@ extern NSInteger const ASDefaultDrawingPriority; @interface ASDisplayNode (UIViewBridge) /** - * Marks the view as needing display. Convenience for use whether the view / layer is loaded or not. Safe to call - * from a background thread. + * Marks the view as needing display. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. */ - (void)setNeedsDisplay; /** * Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread. + * + * If the node determines its own desired layout size will change in the next layout pass, it will propagate this + * information up the tree so its parents can have a chance to consider and apply if necessary the new size onto the node. + * + * Note: ASCellNode has special behavior in that calling this method will automatically notify + * the containing ASTableView / ASCollectionView that the cell should be resized, if necessary. */ - (void)setNeedsLayout; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c729173491..c9a7466c74 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -704,7 +704,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Layout -- (void)invalidateSize +- (void)setNeedsLayoutFromAbove { ASDisplayNodeAssertThreadAffinity(self); @@ -718,7 +718,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) __instanceLock__.unlock(); // Cause supernode's layout to be invalidated // We need to release the lock to prevent a deadlock - [supernode invalidateSize]; + [supernode setNeedsLayoutFromAbove]; return; } @@ -733,8 +733,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; } - // Check if the returned layout has a different size as the current bounds + // Perform a measurement pass to get the current layout + // It's important to differentiate between layout and measure pass here. Calling `layoutThatFits:` just perform a + // measure pass and no layout pass immediately. If a layout pass wold be forced via `layoutIfNeeded` it could cause an + // infinite loop as in `__layout` we check if the size changed and we are just to inform the node that the size changed ASLayout *layout = [self layoutThatFits:constrainedSize]; + + // Check if the returned layout has a different size as the current bounds if (CGSizeEqualToSize(oldSize, layout.size) == NO) { // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) // that we need it to change our bounds size. @@ -1415,11 +1420,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self invalidateCalculatedLayout]; } -- (void)__layoutIfNeeded -{ - // For now this is a no op. -} - - (void)__setNeedsDisplay { BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); @@ -1540,7 +1540,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // If the size of the new layout to apply did change from the current bounds, invalidate the whole tree up // so the root node can handle a resizing if necessary if (CGSizeEqualToSize(ASCeilSizeValues(bounds.size), pendingLayout->layout.size) == NO) { - [self invalidateSize]; + [self setNeedsLayoutFromAbove]; } if (didCreateNewContext) { @@ -1570,7 +1570,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) CGRect bounds = self.threadSafeBounds; // Checkout if constrained size of pending or calculated display node layout can be used - if (_pendingDisplayNodeLayout != nullptr && CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { + if (_pendingDisplayNodeLayout != nullptr && + CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { // We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node // layout constrained size return _pendingDisplayNodeLayout->constrainedSize; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 69ec54dc54..aca1855e7b 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1567,11 +1567,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (ASSizeRange)constrainedSizeForNode:(ASCellNode *)node -{ - return [self dataController:self.dataController constrainedSizeForNodeAtIndexPath:[self indexPathForNode:node]]; -} - - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index cefa27e858..dc22cdf736 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -181,7 +181,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. */ -- (void)invalidateSize; +- (void)setNeedsLayoutFromAbove; /** * @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes diff --git a/examples/ASDKLayoutTransition/Sample/ViewController.m b/examples/ASDKLayoutTransition/Sample/ViewController.m index eb15153b63..789d5029c4 100644 --- a/examples/ASDKLayoutTransition/Sample/ViewController.m +++ b/examples/ASDKLayoutTransition/Sample/ViewController.m @@ -84,7 +84,6 @@ - (void)buttonPressed:(id)sender { self.enabled = !self.enabled; - [self transitionLayoutWithAnimation:YES shouldMeasureAsync:NO measurementCompletion:nil]; } diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index a838ba645d..c22357036e 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -105,11 +105,12 @@ - (void)updateButtonNodeLayout { - [self.buttonNode sizeToFit]; - self.buttonNode.frame = CGRectMake((self.view.bounds.size.width - self.buttonNode.bounds.size.width) / 2.0, + //[self.buttonNode sizeToFit]; + CGSize size = [self.buttonNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))].size; + self.buttonNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, 100, - self.buttonNode.bounds.size.width, - self.buttonNode.bounds.size.height); + size.width, + size.height); //CGSize s = [self.buttonNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; //self.buttonNode.frame = CGRectMake(100, 100, s.width, s.height); @@ -141,7 +142,7 @@ //return; // Use the bounds of the view and get the fitting size // This does not have any side effects, but can be called on the main thread without any problems - CGSize size = [self.sizingNode sizeThatFits:CGSizeMake(INFINITY, 100.0)]; + CGSize size = [self.sizingNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, 100.0))].size; //size.width -= 10; //[self.sizingNode setNeedsLayout]; self.sizingNode.frame = CGRectMake((CGRectGetWidth(self.view.bounds) - size.width) / 2.0, diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m index 674b545b09..d7ff1c4e86 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.m +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -129,7 +129,7 @@ /*if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { [self.sizingDelegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; }*/ - [self invalidateSize]; + //[self invalidateSize]; } #pragma mark - ASDisplayNode From b7629983360d52e170bf73395bfe50ce97193fc4 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 17 Nov 2016 18:21:27 -0800 Subject: [PATCH 30/39] Add detection for a loop if invalidation of a size is happening multiple times --- AsyncDisplayKit/ASDisplayNode.mm | 10 +++++++++- AsyncDisplayKit/Private/ASDisplayNodeLayout.h | 5 +++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c9a7466c74..83ba52dca2 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1539,7 +1539,15 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // If the size of the new layout to apply did change from the current bounds, invalidate the whole tree up // so the root node can handle a resizing if necessary - if (CGSizeEqualToSize(ASCeilSizeValues(bounds.size), pendingLayout->layout.size) == NO) { + if (pendingLayout->requestedLayoutFromAbove == NO && + CGSizeEqualToSize(ASCeilSizeValues(bounds.size), pendingLayout->layout.size) == NO) { + // The layout that we have specifies that this node (self) would like to be a different size + // than it currently is. Because that size has been computed within the constrainedSize, we + // expect that doing setNeedsLayoutFromAbove will result in our parent resizing us to this. + // However, in some cases apps may manually interfere with this (setting a different bounds). + // In this case, we need to detect that we've already asked to be resized to match this + // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. + pendingLayout->requestedLayoutFromAbove = YES; [self setNeedsLayoutFromAbove]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayout.h b/AsyncDisplayKit/Private/ASDisplayNodeLayout.h index 64837acc25..a9a2d5a8a7 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeLayout.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeLayout.h @@ -24,6 +24,7 @@ struct ASDisplayNodeLayout { ASLayout *layout; ASSizeRange constrainedSize; CGSize parentSize; + BOOL requestedLayoutFromAbove; BOOL _dirty; /* @@ -33,13 +34,13 @@ struct ASDisplayNodeLayout { * @param parentSize Parent size used to create the layout */ ASDisplayNodeLayout(ASLayout *layout, ASSizeRange constrainedSize, CGSize parentSize) - : layout(layout), constrainedSize(constrainedSize), parentSize(parentSize), _dirty(NO) {}; + : layout(layout), constrainedSize(constrainedSize), parentSize(parentSize), requestedLayoutFromAbove(NO), _dirty(NO) {}; /* * Creates a layout without any layout associated. By default this display node layout is dirty. */ ASDisplayNodeLayout() - : layout(nil), constrainedSize({{0, 0}, {0, 0}}), parentSize({0, 0}), _dirty(YES) {}; + : layout(nil), constrainedSize({{0, 0}, {0, 0}}), parentSize({0, 0}), requestedLayoutFromAbove(NO), _dirty(YES) {}; /** * Returns if the display node layout is dirty as it was invalidated or it was created without a layout. From cb37f10ed186f4b95eda578b3e3a91be6300440c Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 17 Nov 2016 18:36:34 -0800 Subject: [PATCH 31/39] Revert sample code --- AsyncDisplayKit/Private/_ASPendingState.mm | 2 +- .../Sample/DetailViewController.m | 158 ----------------- .../Sample/SampleSizingNode.h | 14 -- .../Sample/SampleSizingNode.m | 159 ------------------ examples/Kittens/Sample/KittenNode.mm | 4 +- examples/SocialAppLayout/Sample/PostNode.m | 2 +- 6 files changed, 4 insertions(+), 335 deletions(-) diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index f6c6685a19..c01470e258 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -763,7 +763,7 @@ static BOOL defaultAllowsEdgeAntialiasing = NO; if (flags.needsLayout) [layer setNeedsLayout]; - + if (flags.setAsyncTransactionContainer) layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer; diff --git a/examples/ASViewController/Sample/DetailViewController.m b/examples/ASViewController/Sample/DetailViewController.m index c22357036e..494efd4ffd 100644 --- a/examples/ASViewController/Sample/DetailViewController.m +++ b/examples/ASViewController/Sample/DetailViewController.m @@ -19,167 +19,9 @@ #import #import "DetailRootNode.h" -#import "SampleSizingNode.h" - -@interface DetailViewController ()// -@property (strong, nonatomic) SampleSizingNode *sizingNode; - -@property (strong, nonatomic) ASNetworkImageNode *imageNode; -@property (strong, nonatomic) ASButtonNode *buttonNode; - -@end @implementation DetailViewController -#pragma mark - Lifecycle - -- (instancetype)initWithNode:(DetailRootNode *)node -{ - self = [super initWithNode:node]; - - // Set the sizing delegate of the root node to the container - _sizingNode = [SampleSizingNode new]; - _sizingNode.autoresizingMask = UIViewAutoresizingNone; - //_sizingNode.sizingDelegate = self; - - _imageNode = [ASNetworkImageNode new]; - _imageNode.needsDisplayOnBoundsChange = YES; - _imageNode.backgroundColor = [UIColor brownColor]; - _imageNode.style.preferredSize = CGSizeMake(100, 100); - _imageNode.URL = [NSURL URLWithString:@"http://www.classicwings-bavaria.com/bavarian-pictures/chitty-chitty-bang-bang-castle.jpg"]; - - _buttonNode = [ASButtonNode new]; - _buttonNode.backgroundColor = [UIColor yellowColor]; - [_buttonNode setTitle:@"Some Title" withFont:nil withColor:nil forState:ASControlStateNormal]; - [_buttonNode setTitle:@"Some Bla" withFont:nil withColor:[UIColor orangeColor] forState:ASControlStateHighlighted]; - [_buttonNode addTarget:self action:@selector(buttonAction:) forControlEvents:ASControlNodeEventTouchUpInside]; - //_buttonNode.sizingDelegate = self; - - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - [self.view addSubnode:self.sizingNode]; - [self.view addSubnode:self.imageNode]; - [self.view addSubnode:self.buttonNode]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Initial size of sizing node - //self.sizingNode.frame = CGRectMake(100, 100, 50, 50); - - //[self displayNodeDidInvalidateSize:self.buttonNode]; - - // Initial size for image node -// self.imageNode.frame = CGRectMake(50, 70, 100, 100); -// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ -// self.imageNode.frame = CGRectMake(50, 70, 70, 50); -// //[self.imageNode setNeedsLayout]; -// //[self.imageNode setNeedsDisplay]; -// }); - - // Start some timer to chang ethe size randomly - [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { - //[self updateNodeLayoutRandom]; - }]; -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; - - // Updat the sizing for the button node - [self updateButtonNodeLayout]; - - // Update the sizing node layout - [self updateNodeLayout]; -} - -#pragma mark - Update the node based on the new size - -- (void)updateButtonNodeLayout -{ - //[self.buttonNode sizeToFit]; - CGSize size = [self.buttonNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))].size; - self.buttonNode.frame = CGRectMake((self.view.bounds.size.width - size.width) / 2.0, - 100, - size.width, - size.height); - - //CGSize s = [self.buttonNode sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; - //self.buttonNode.frame = CGRectMake(100, 100, s.width, s.height); -} - -// The sizing delegate will get callbacks if the size did invalidate of the display node. It's the job of the delegate -// to get the new size from the display node and update the frame based on the returned size -//- (void)displayNodeDidInvalidateSize:(ASDisplayNode *)displayNode -//{ -// if (displayNode == self.buttonNode) { -// [self updateButtonNodeLayout]; -// return; -// } -// -// [self updateNodeLayout]; -// -// /*dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ -// [self updateNodeLayoutRandom]; -// });*/ -// -// /*[NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) { -// [self updateNodeLayoutRandom]; -// }];*/ -//} - -- (void)updateNodeLayout -{ - // Adjust the layout on the new layout - //return; - // Use the bounds of the view and get the fitting size - // This does not have any side effects, but can be called on the main thread without any problems - CGSize size = [self.sizingNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, 100.0))].size; - //size.width -= 10; - //[self.sizingNode setNeedsLayout]; - self.sizingNode.frame = CGRectMake((CGRectGetWidth(self.view.bounds) - size.width) / 2.0, - (CGRectGetHeight(self.view.bounds) - size.height) / 2.0, - size.width, - size.height); - - //dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - // Decrease the frame a bit - self.sizingNode.frame = CGRectInset(self.sizingNode.frame, 10, 10); - //}); -} - -- (void)updateNodeLayoutRandom -{ - CGRect bounds = self.view.bounds; - - // Pick a randome width and height and set the frame of the node - CGSize size = CGSizeZero; - size.width = arc4random_uniform(CGRectGetWidth(bounds)); - size.height = arc4random_uniform(CGRectGetHeight(bounds)); - - //[self.sizingNode setNeedsLayout]; - self.sizingNode.frame = CGRectMake((CGRectGetWidth(bounds) - size.width) / 2.0, - (CGRectGetHeight(bounds) - size.height) / 2.0, - size.width, - size.height); - -} - -#pragma mark - - -- (void)buttonAction:(id)sender -{ - NSLog(@"Button Sender"); -} - #pragma mark - Rotation - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator diff --git a/examples/ASViewController/Sample/SampleSizingNode.h b/examples/ASViewController/Sample/SampleSizingNode.h index 57ecdb7df0..e69de29bb2 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.h +++ b/examples/ASViewController/Sample/SampleSizingNode.h @@ -1,14 +0,0 @@ -// -// SampleSizingNode.h -// Sample -// -// Created by Michael Schneider on 11/10/16. -// Copyright © 2016 AsyncDisplayKit. All rights reserved. -// - -#import - - -@interface SampleSizingNode : ASDisplayNode - -@end diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m index d7ff1c4e86..e69de29bb2 100644 --- a/examples/ASViewController/Sample/SampleSizingNode.m +++ b/examples/ASViewController/Sample/SampleSizingNode.m @@ -1,159 +0,0 @@ -// -// SampleSizingNode.m -// Sample -// -// Created by Michael Schneider on 11/10/16. -// Copyright © 2016 AsyncDisplayKit. All rights reserved. -// - -#import "SampleSizingNode.h" - -@interface SampleSizingNodeSubnode : ASDisplayNode -@property (strong, nonatomic) ASTextNode *textNode; -@end - -@implementation SampleSizingNodeSubnode - -- (void)layout -{ - [super layout]; - - // Manual layout after the normal layout engine did it's job - // Calculated size can be used after the layout spec pass happened - //self.textNode.frame = CGRectMake(self.textNode.frame.origin.x, self.textNode.frame.origin.y, self.textNode.calculatedSize.width, 20); -} - -@end - -@interface SampleSizingNode () -@property (nonatomic, assign) NSInteger state; - -@property (nonatomic, strong) SampleSizingNodeSubnode *subnode; -@property (nonatomic, strong) ASTextNode *textNode; -@property (nonatomic, strong) ASNetworkImageNode *imageNode; -@end - -@implementation SampleSizingNode - -- (instancetype)init -{ - self = [super init]; - if (self) { - - self.automaticallyManagesSubnodes = YES; - - self.backgroundColor = [UIColor greenColor]; - - _textNode = [ASTextNode new]; - _textNode.backgroundColor = [UIColor blueColor]; - - _imageNode = [ASNetworkImageNode new]; - _imageNode.URL = [NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Mont_Blanc_oct_2004.JPG/280px-Mont_Blanc_oct_2004.JPG"]; - _imageNode.backgroundColor = [UIColor brownColor]; - _imageNode.needsDisplayOnBoundsChange = YES; - _imageNode.style.height = ASDimensionMakeWithFraction(1.0); - _imageNode.style.width = ASDimensionMake(50.0); - - - _subnode = [SampleSizingNodeSubnode new]; - _subnode.textNode = _textNode; - _subnode.backgroundColor = [UIColor redColor]; - _subnode.automaticallyManagesSubnodes = YES; - - // Layout description via layoutSpecBlock - __weak __typeof(self) weakSelf = self; - _subnode.layoutSpecBlock = ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { - - UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); - ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:weakSelf.textNode]; - textInsetSpec.style.flexShrink = 1.0; - - return [ASStackLayoutSpec - stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - children:@[weakSelf.imageNode, textInsetSpec]]; - //return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit children:@[_imageNode, insetSpec]]; - }; - - _state = 0; - } - return self; -} - -- (void)didLoad -{ - [super didLoad]; - - - // Initial state - self.state = 0; - - // Simulate a state change of the node - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.state = 1; - }); -} - -#pragma mark - State Management - -- (void)setState:(NSInteger)state -{ - _state = state; - - // Set text based on state - NSString *text = state == 0 ? @"Bla Bla" : @"Bla Blaa sd fkj as;l dkf"; - self.textNode.attributedText = [[NSAttributedString alloc] initWithString:text]; - - //self.imageNode.style.width = state == 0 ? ASDimensionMake(30.0) : ASDimensionMake(50.0); - - // Let the root node know there can be a size change happened. If we will not call this the text node will just - // use it's own bounds and layout again in the next layout pass what can result in truncation - [self invalidateCalculatedLayoutSizingBlaBla]; -} - -// Invalidates the current layout and bubbles up the setNeedsLayout call to the root node. The root node will inform -// the sizing delegate that a size change did happen and it's up to it to decide if the bounds of the root node should -// change based on this request or not. If no change happened the layout will happen in all subnodes based on the current -// set bounds -- (void)invalidateCalculatedLayoutSizingBlaBla -{ - // Invalidate the layout for now and bubble it up until the root node to let the size provider know that - // that a size change could have happened - // --> Do we even need to invalidate the layout? - [self setNeedsLayout]; - - // If someone calls `invalidateBlaBla TBD` we have to inform the sizing delegate of the root node to be able - // to let them now that a size change happened and it needs to calculate a new layout / size for this node hierarchy - /*if ([self.sizingDelegate respondsToSelector:@selector(displayNodeDidInvalidateSize:)]) { - [self.sizingDelegate performSelector:@selector(displayNodeDidInvalidateSize:) withObject:self]; - }*/ - //[self invalidateSize]; -} - -#pragma mark - ASDisplayNode - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - // Layout description based on state - // UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); - // return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_textNode]; - - //return [ASWrapperLayoutSpec wrapperWithLayoutElement:self.subnode]; - return [ASCenterLayoutSpec - centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY - sizingOptions:ASCenterLayoutSpecSizingOptionDefault - child:self.subnode]; -} - -- (void)layout -{ - [super layout]; - - // Layout after the official layout pass happened - //self.subnode.frame = CGRectMake(self.subnode.frame.origin.x, self.subnode.frame.origin.y, 100, self.calculatedSize.height); -} - - -@end diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 42a94a055a..67e586b4a7 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -201,7 +201,7 @@ static const CGFloat kInnerPadding = 10.0f; - (void)toggleImageEnlargement { _isImageEnlarged = !_isImageEnlarged; - [self invalidateSize]; + [self setNeedsLayout]; } - (void)toggleNodesSwap @@ -211,7 +211,7 @@ static const CGFloat kInnerPadding = 10.0f; [UIView animateWithDuration:0.15 animations:^{ self.alpha = 0; } completion:^(BOOL finished) { - [self invalidateSize]; + [self setNeedsLayout]; [self.view layoutIfNeeded]; [UIView animateWithDuration:0.15 animations:^{ diff --git a/examples/SocialAppLayout/Sample/PostNode.m b/examples/SocialAppLayout/Sample/PostNode.m index b4abad7a64..3206b00d99 100644 --- a/examples/SocialAppLayout/Sample/PostNode.m +++ b/examples/SocialAppLayout/Sample/PostNode.m @@ -339,7 +339,7 @@ - (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { - [self invalidateSize]; + [self setNeedsLayout]; } @end From fc747ca58af0649429881a7b337616b0e93c3f25 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 18 Nov 2016 08:08:26 -0800 Subject: [PATCH 32/39] Small code improvements --- AsyncDisplayKit/ASDisplayNode.mm | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 83ba52dca2..df06fef14d 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1442,6 +1442,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Performing layout on a zero-bounds view often results in frame calculations // with negative sizes after applying margins, which will cause // measureWithSizeRange: on subnodes to assert. + LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); return; } @@ -1451,12 +1452,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self _locked_measureNodeWithBoundsIfNecessary:bounds]; _pendingDisplayNodeLayout = nullptr; - // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer - // was created - if ([self _shouldHavePlaceholderLayer]) { - [self _setupPlaceholderLayerIfNeeded]; - } - _placeholderLayer.frame = bounds; + [self _locked_layoutPlaceholderIfNecessary]; [self layout]; [self layoutDidFinish]; @@ -1474,13 +1470,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } - // 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 - 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); - return; - } - // Check if we can reuse the calculated display node layout. We prefer the _pendingDisplayNodeLayout over the // _calculatedDisplayNodeLayout though if (_pendingDisplayNodeLayout == nullptr) { @@ -1528,7 +1517,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Use as default constrained size the bounds CGSize parentSize = bounds.size; - ASSizeRange constrainedSize = [self constrainedSizeForLayoutPass]; + ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; return std::make_shared( [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], @@ -1573,7 +1562,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } -- (ASSizeRange)constrainedSizeForLayoutPass +- (ASSizeRange)_locked_constrainedSizeForLayoutPass { CGRect bounds = self.threadSafeBounds; @@ -1595,6 +1584,16 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } +- (void)_locked_layoutPlaceholderIfNecessary +{ + // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer + // was created + if ([self _shouldHavePlaceholderLayer]) { + [self _setupPlaceholderLayerIfNeeded]; + } + _placeholderLayer.frame = self.threadSafeBounds; +} + - (void)layoutDidFinish { // Hook for subclasses From f9c70470d8bb5f093dfe9f08ba944f4fc2a3259f Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 18 Nov 2016 09:50:49 -0800 Subject: [PATCH 33/39] Address another round of comments --- AsyncDisplayKit/ASCellNode.mm | 2 +- AsyncDisplayKit/ASDisplayNode.h | 2 +- AsyncDisplayKit/ASDisplayNode.mm | 54 +++++++++++-------- .../Private/ASDisplayNode+FrameworkPrivate.h | 2 +- .../ASDisplayNodeTestsHelper.m | 7 ++- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 0b90180562..7ddd50cab7 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -118,7 +118,7 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init _viewControllerNode.frame = self.bounds; } -- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)newSize +- (void)_locked_displayNodeDidInvalidateSizeNewSize:(CGSize)newSize { CGSize oldSize = self.bounds.size; if (CGSizeEqualToSize(oldSize, newSize) == NO) { diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 6e31d5154f..18751b1120 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -796,7 +796,7 @@ extern NSInteger const ASDefaultDrawingPriority; /** - * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread. + * @abstract Invalidates the layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread. * * @discussion It is called right after the measurement and before -animateLayoutTransition:. * diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index df06fef14d..076d44f89b 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -735,7 +735,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Perform a measurement pass to get the current layout // It's important to differentiate between layout and measure pass here. Calling `layoutThatFits:` just perform a - // measure pass and no layout pass immediately. If a layout pass wold be forced via `layoutIfNeeded` it could cause an + // measure pass and no layout pass immediately. If a layout pass would be forced via `layoutIfNeeded` it could cause an // infinite loop as in `__layout` we check if the size changed and we are just to inform the node that the size changed ASLayout *layout = [self layoutThatFits:constrainedSize]; @@ -743,13 +743,13 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (CGSizeEqualToSize(oldSize, layout.size) == NO) { // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) // that we need it to change our bounds size. - [self displayNodeDidInvalidateSizeNewSize:layout.size]; + [self _locked_displayNodeDidInvalidateSizeNewSize:layout.size]; } __instanceLock__.unlock(); } -- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size +- (void)_locked_displayNodeDidInvalidateSizeNewSize:(CGSize)size { ASDisplayNodeAssertThreadAffinity(self); @@ -829,14 +829,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion { - if (_calculatedDisplayNodeLayout->layout == nil) { - // No measure pass happened before, it's not possible to reuse the constrained size for the transition - // Using CGSizeZero for the sizeRange can cause negative values in client layout code. - return; - } - [self setNeedsLayout]; - [self transitionLayoutWithSizeRange:_calculatedDisplayNodeLayout->constrainedSize + [self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass] animated:animated shouldMeasureAsync:shouldMeasureAsync measurementCompletion:completion]; @@ -848,9 +842,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion { - // Passed constrainedSize is the the same as the node's current constrained size it's a noop ASDisplayNodeAssertMainThread(); - if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, constrainedSize.max)) { + + // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of + // the layout transition + if ([self _isInvolvedInLayoutTransition]) { return; } @@ -859,20 +855,23 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); } + // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling int32_t transitionID = [self _startNewTransition]; - // Move all subnodes in a pending state + // Move all subnodes in layout pending state for this transition ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { ASDisplayNodeAssert([node _isTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one."); node.hierarchyState |= ASHierarchyStateLayoutPending; node.pendingTransitionID = transitionID; }); + // Transition block that executes the layout transition void (^transitionBlock)(void) = ^{ if ([self _shouldAbortTransitionWithID:transitionID]) { return; } + // Perform a full layout creation pass with passed in constrained size to create the new layout for the transition ASLayout *newLayout; { ASDN::MutexLocker l(__instanceLock__); @@ -905,7 +904,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return; } - // Update display node layout + // Update calculated layout auto previousLayout = _calculatedDisplayNodeLayout; auto pendingLayout = std::make_shared( newLayout, @@ -944,6 +943,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) }); }; + // Start transition based on flag on current or background thread if (shouldMeasureAsync) { ASPerformBlockOnBackgroundThread(transitionBlock); } else { @@ -971,6 +971,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _transitionInProgress; } +- (BOOL)_isInvolvedInLayoutTransition +{ + ASDN::MutexLocker l(__instanceLock__); + if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { + ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); + if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) { + return YES; + } + } + return NO; +} + /// Starts a new transition and returns the transition id - (int32_t)_startNewTransition { @@ -1463,11 +1475,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of // the layout transition - if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); - if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) { - return; - } + if ([self _isInvolvedInLayoutTransition]) { + return; } // Check if we can reuse the calculated display node layout. We prefer the _pendingDisplayNodeLayout over the @@ -1486,13 +1495,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) BOOL didCreateNewContext = NO; BOOL didOverrideExistingContext = NO; BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); - ASLayoutElementContext context; - if (ASLayoutElementContextIsNull(ASLayoutElementGetCurrentContext())) { + ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); + if (ASLayoutElementContextIsNull(context)) { context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); ASLayoutElementSetCurrentContext(context); didCreateNewContext = YES; } else { - context = ASLayoutElementGetCurrentContext(); if (context.needsVisualizeNode != shouldVisualizeLayout) { context.needsVisualizeNode = shouldVisualizeLayout; ASLayoutElementSetCurrentContext(context); @@ -1500,7 +1508,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } - // Figure out previos and pending layout for layout transition + // Figure out previous and pending layouts for layout transition auto previousLayout = _calculatedDisplayNodeLayout; auto pendingLayout = [=]() -> std::shared_ptr { // Check if the pending display node layout can be used to transition to diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index dc22cdf736..a7025a2db2 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -187,7 +187,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat * @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes * size is invalidated and may need to result in a different size as the current calculated size. */ -- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)newSize; +- (void)_locked_displayNodeDidInvalidateSizeNewSize:(CGSize)newSize; @end diff --git a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m index 0a64095770..d1721cef51 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTestsHelper.m @@ -44,11 +44,14 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block) return passed; } -void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size) { +void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size) +{ CGSize sizeThatFits = [node layoutThatFits:ASSizeRangeMake(size)].size; node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; } -void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) { + +void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) +{ CGSize sizeThatFits = [node layoutThatFits:sizeRange].size; node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; } From eef4a61ea0e29f317f47c826521cd75f50dee444 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 18 Nov 2016 11:08:46 -0800 Subject: [PATCH 34/39] Add checks to bail out early in layout transition --- AsyncDisplayKit/ASDisplayNode.mm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 076d44f89b..ab19f7ed02 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -782,6 +782,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { ASDN::MutexLocker l(__instanceLock__); + + // If multiple layout transitions are in progress it can happen that an invalid one is still trying to do a measurement + // before it get's cancelled. In this case we should not touch any layout and return a no op layout + if ([self _isLayoutTransitionInvalid]) { + return [ASLayout layoutWithLayoutElement:self size:{0, 0}]; + } if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) { ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self); @@ -846,7 +852,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of // the layout transition - if ([self _isInvolvedInLayoutTransition]) { + if ([self _isLayoutTransitionInvalid]) { return; } @@ -971,11 +977,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _transitionInProgress; } -- (BOOL)_isInvolvedInLayoutTransition +- (BOOL)_isLayoutTransitionInvalid { ASDN::MutexLocker l(__instanceLock__); if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) { - ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); + ASLayoutElementContext context = ASLayoutElementGetCurrentContext(); if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) { return YES; } @@ -1475,7 +1481,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of // the layout transition - if ([self _isInvolvedInLayoutTransition]) { + if ([self _isLayoutTransitionInvalid]) { return; } From 8c778c54106cbd6d9e231f27c20957eed06c9120 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 18 Nov 2016 11:15:18 -0800 Subject: [PATCH 35/39] Remove SampleSizingNode --- examples/ASViewController/Sample/SampleSizingNode.h | 0 examples/ASViewController/Sample/SampleSizingNode.m | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/ASViewController/Sample/SampleSizingNode.h delete mode 100644 examples/ASViewController/Sample/SampleSizingNode.m diff --git a/examples/ASViewController/Sample/SampleSizingNode.h b/examples/ASViewController/Sample/SampleSizingNode.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/ASViewController/Sample/SampleSizingNode.m b/examples/ASViewController/Sample/SampleSizingNode.m deleted file mode 100644 index e69de29bb2..0000000000 From ed4f7eff73c387099f67d276c7a081630899b5f2 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 18 Nov 2016 11:35:45 -0800 Subject: [PATCH 36/39] Force layout pass in `ASViewController` `viewWillAppear:` to trigger automatic subnode management A measure as well as layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. into the hierarchy before UIKit applies the scroll view inset adjustments, if automatic subnode management is enabled. Otherwise the insets would not be applied. --- AsyncDisplayKit/ASViewController.mm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASViewController.mm b/AsyncDisplayKit/ASViewController.mm index dfb41bfc7f..606f667c10 100644 --- a/AsyncDisplayKit/ASViewController.mm +++ b/AsyncDisplayKit/ASViewController.mm @@ -135,10 +135,11 @@ ASVisibilityDidMoveToParentViewController; [super viewWillAppear:animated]; _ensureDisplayed = YES; - // We do this early layout because we need to get any ASCollectionNodes etc. into the - // hierarchy before UIKit applies the scroll view inset adjustments, if you are using - // automatic subnode management. + // A measure as well as layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. + // into the hierarchy before UIKit applies the scroll view inset adjustments, if automatic subnode management + // is enabled. Otherwise the insets would not be applied. [_node layoutThatFits:[self nodeConstrainedSize]]; + [_node.view layoutIfNeeded]; [_node recursivelyFetchData]; From acc080ab9c4329ad6ec0f9a9a4eba3905ef7a9cd Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 18 Nov 2016 12:05:12 -0800 Subject: [PATCH 37/39] Revert setting isMutable to true in ASLayoutSpecSnapshotTestHelper --- AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m index 90a40f988c..5d555bdde4 100644 --- a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m +++ b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m @@ -56,10 +56,6 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - // As the layout spec under test can be used multiple times we set the isMutable to NO in here. This should only - // be done in tests and never in production codes - _layoutSpecUnderTest.isMutable = YES; - return _layoutSpecUnderTest; } From 66ad9bd5e3d5174618d789dc4690fe01556977cb Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 19 Nov 2016 06:46:21 -0800 Subject: [PATCH 38/39] Apply patch from Scott --- AsyncDisplayKit/ASDisplayNode.mm | 143 +++++++++++++++---------------- 1 file changed, 67 insertions(+), 76 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ab19f7ed02..c55aa63e4a 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -713,36 +713,33 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Mark the node for layout in the next layout pass [self setNeedsLayout]; + // Escalate to the root; entire tree must allow adjustments so the layout fits the new child. + // Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack) ASDisplayNode *supernode = _supernode; if (supernode) { + // Threading model requires that we unlock before calling a method on our parent. __instanceLock__.unlock(); - // Cause supernode's layout to be invalidated - // We need to release the lock to prevent a deadlock [supernode setNeedsLayoutFromAbove]; return; } - // We are the root node and need to re-flow the layout; one of our children requested to have its size re-set. - CGSize oldSize = self.bounds.size; + // We are the root node and need to re-flow the layout; at least one child needs a new size. + CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size); // Figure out constrainedSize to use - ASSizeRange constrainedSize = ASSizeRangeMake(self.bounds.size); + ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout); if (_pendingDisplayNodeLayout != nullptr) { constrainedSize = _pendingDisplayNodeLayout->constrainedSize; } else if (_calculatedDisplayNodeLayout->layout != nil) { constrainedSize = _calculatedDisplayNodeLayout->constrainedSize; } - // Perform a measurement pass to get the current layout - // It's important to differentiate between layout and measure pass here. Calling `layoutThatFits:` just perform a - // measure pass and no layout pass immediately. If a layout pass would be forced via `layoutIfNeeded` it could cause an - // infinite loop as in `__layout` we check if the size changed and we are just to inform the node that the size changed + // Perform a measurement pass to get the full tree layout, adapting to the child's new size. ASLayout *layout = [self layoutThatFits:constrainedSize]; - - // Check if the returned layout has a different size as the current bounds - if (CGSizeEqualToSize(oldSize, layout.size) == NO) { - // If the size of the layout changes inform our container (e.g ASTableView, ASCollectionView, ASViewController, ...) - // that we need it to change our bounds size. + + // Check if the returned layout has a different size than our current bounds. + if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) { + // If so, inform our container we need an update (e.g Table, Collection, ViewController, etc). [self _locked_displayNodeDidInvalidateSizeNewSize:layout.size]; } @@ -850,8 +847,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertMainThread(); - // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of - // the layout transition + // Check if we are a subnode in a layout transition. + // In this case no measurement is needed as we're part of the layout transition. if ([self _isLayoutTransitionInvalid]) { return; } @@ -1465,8 +1462,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // This method will confirm that the layout is up to date (and update if needed). - // Importantly, it will also APPLY the layout to all of our subnodes if there is not already another layout - // transition in progress. + // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). [self _locked_measureNodeWithBoundsIfNecessary:bounds]; _pendingDisplayNodeLayout = nullptr; @@ -1479,18 +1475,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) /// Needs to be called with lock held - (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds { - // Check if it's a subnode in a layout transition. In this case no measurement is needed as it's part of - // the layout transition + // Check if we are a subnode in a layout transition. + // In this case no measurement is needed as it's part of the layout transition if ([self _isLayoutTransitionInvalid]) { return; } - // Check if we can reuse the calculated display node layout. We prefer the _pendingDisplayNodeLayout over the - // _calculatedDisplayNodeLayout though + CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); + + // Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest) + // If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below). if (_pendingDisplayNodeLayout == nullptr) { - if (_calculatedDisplayNodeLayout->isDirty() == NO && - CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { - // Reuse calculatedDisplayNodeLayout for layout pass + if (_calculatedDisplayNodeLayout->isDirty() == NO + && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES + || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { return; } } @@ -1515,96 +1513,89 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // Figure out previous and pending layouts for layout transition - auto previousLayout = _calculatedDisplayNodeLayout; - auto pendingLayout = [=]() -> std::shared_ptr { - // Check if the pending display node layout can be used to transition to - if (_pendingDisplayNodeLayout != nullptr && - _pendingDisplayNodeLayout->isDirty() == NO && - CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { - // We assume the _pendingDisplayNodeLayout was created by layoutThatFits: to set the size of the node - // now it's time to apply it and to become the _calculatedDisplayNodeLayout - return _pendingDisplayNodeLayout; - } + std::shared_ptr nextLayout = _pendingDisplayNodeLayout; + #define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout) - // The _pendingDisplayNodeLayout can not be used to be applied we need to figure out we need to calculate a new - // layout based on a constrainedSize - - // Use as default constrained size the bounds - CGSize parentSize = bounds.size; + // nextLayout was likely created by a call to layoutThatFits:, check if is valid and can be applied. + // If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr-> + if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) { + // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; + ASLayout *layout = [self calculateLayoutThatFits:constrainedSize + restrictedToSize:self.style.size + relativeToParentSize:boundsSizeForLayout]; - return std::make_shared( - [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, - parentSize - ); - }(); - - // If the size of the new layout to apply did change from the current bounds, invalidate the whole tree up - // so the root node can handle a resizing if necessary - if (pendingLayout->requestedLayoutFromAbove == NO && - CGSizeEqualToSize(ASCeilSizeValues(bounds.size), pendingLayout->layout.size) == NO) { - // The layout that we have specifies that this node (self) would like to be a different size - // than it currently is. Because that size has been computed within the constrainedSize, we - // expect that doing setNeedsLayoutFromAbove will result in our parent resizing us to this. - // However, in some cases apps may manually interfere with this (setting a different bounds). - // In this case, we need to detect that we've already asked to be resized to match this - // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. - pendingLayout->requestedLayoutFromAbove = YES; - [self setNeedsLayoutFromAbove]; + nextLayout = std::make_shared(layout, constrainedSize, boundsSizeForLayout); } - + if (didCreateNewContext) { ASLayoutElementClearCurrentContext(); } else if (didOverrideExistingContext) { context.needsVisualizeNode = !context.needsVisualizeNode; ASLayoutElementSetCurrentContext(context); } + + // If our new layout's desired size for self doesn't match current size, ask our parent to update it. + // This can occur for either pre-calculated or newly-calculated layouts. + if (nextLayout->requestedLayoutFromAbove == NO + && CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) { + // The layout that we have specifies that this node (self) would like to be a different size + // than it currently is. Because that size has been computed within the constrainedSize, we + // expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this. + // However, in some cases apps may manually interfere with this (setting a different bounds). + // In this case, we need to detect that we've already asked to be resized to match this + // particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. + nextLayout->requestedLayoutFromAbove = YES; + [self setNeedsLayoutFromAbove]; + } - // Finally transition to pendingLayout - ASDisplayNodeAssertNotNil(pendingLayout->layout, @"pendingLayout->layout should not be nil! %@", self); - + // Prepare to transition to nextLayout + ASDisplayNodeAssertNotNil(nextLayout->layout, @"nextLayout->layout should not be nil! %@", self); _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self - pendingLayout:pendingLayout - previousLayout:previousLayout]; + pendingLayout:nextLayout + previousLayout:_calculatedDisplayNodeLayout]; - // Only complete the pending layout transition if the node is not a subnode of a node that is currently - // in a layout transition + // If a parent is currently executing a layout transition, perform our layout application after it. if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) { - // Complete the pending layout transition immediately + // If no transition, apply our new layout immediately (common case). [self _completePendingLayoutTransition]; } } - (ASSizeRange)_locked_constrainedSizeForLayoutPass { - CGRect bounds = self.threadSafeBounds; + // TODO: The logic in -setNeedsLayoutFromAbove seems correct and doesn't use this method. + // logic seems correct. For what case does -this method need to do the CGSizeEqual checks? + // IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK + + CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size); // Checkout if constrained size of pending or calculated display node layout can be used - if (_pendingDisplayNodeLayout != nullptr && - CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { + if (_pendingDisplayNodeLayout != nullptr + && (_pendingDisplayNodeLayout->requestedLayoutFromAbove + || CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, boundsSizeForLayout))) { // We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node // layout constrained size return _pendingDisplayNodeLayout->constrainedSize; - } else if (CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, CGSizeZero) == NO && - CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, ASCeilSizeValues(bounds.size))) { + } else if (_calculatedDisplayNodeLayout->layout != nil + && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove + || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { // We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different return _calculatedDisplayNodeLayout->constrainedSize; } else { // In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can // be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to // the one returned from layoutThatFits: or layoutThatFits: was never called - return ASSizeRangeMake(bounds.size); + return ASSizeRangeMake(boundsSizeForLayout); } } - (void)_locked_layoutPlaceholderIfNecessary { - // Handle placeholder layer creation in case the size of the node changed after the initial placeholder layer - // was created if ([self _shouldHavePlaceholderLayer]) { [self _setupPlaceholderLayerIfNeeded]; } + // Update the placeholderLayer size in case the node size has changed since the placeholder was added. _placeholderLayer.frame = self.threadSafeBounds; } From 8d5fc9efe34e7913db1db9eb9d15aff554ccc8c3 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 19 Nov 2016 06:58:07 -0800 Subject: [PATCH 39/39] We need to invalidate the layout before the transition does start --- AsyncDisplayKit/ASDisplayNode.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c55aa63e4a..1bf8f53a15 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -832,7 +832,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion { - [self setNeedsLayout]; [self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass] animated:animated shouldMeasureAsync:shouldMeasureAsync @@ -858,6 +857,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one."); } + // Invalidate the current layout to be able to measure a new layout based onthe given constrained size + [self setNeedsLayout]; + // Every new layout transition has a transition id associated to check in subsequent transitions for cancelling int32_t transitionID = [self _startNewTransition];