From b273f358f586d71b9e66a18b30e3c505b3946763 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 1 Jan 2016 00:48:44 -0800 Subject: [PATCH] [ASRangeController] Initial implementation of functional-style, ASInterfaceState-based range controller. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 14 ++ AsyncDisplayKit/ASCollectionView.mm | 21 ++- AsyncDisplayKit/ASDisplayNode.mm | 25 ++- AsyncDisplayKit/ASPagerNode.m | 7 +- AsyncDisplayKit/ASTableView.mm | 18 +- .../Details/ASAbstractLayoutController.h | 4 - .../Details/ASAbstractLayoutController.mm | 20 ++- .../ASCollectionViewLayoutController.h | 6 + .../ASCollectionViewLayoutController.mm | 166 +++++++++--------- .../Details/ASFlowLayoutController.mm | 6 +- AsyncDisplayKit/Details/ASLayoutController.h | 10 +- AsyncDisplayKit/Details/ASLayoutRangeType.h | 7 +- AsyncDisplayKit/Details/ASRangeController.h | 23 ++- AsyncDisplayKit/Details/ASRangeController.mm | 68 ++++--- .../Details/ASRangeControllerBeta.mm | 19 +- .../Details/ASRangeHandlerPreload.mm | 4 +- .../Details/ASRangeHandlerRender.mm | 9 +- .../Details/CGRect+ASConvenience.h | 7 +- .../Details/CGRect+ASConvenience.m | 85 +++++++-- .../UICollectionViewLayout+ASConvenience.m | 5 +- .../Private/ASDisplayNode+FrameworkPrivate.h | 1 + .../Private/ASDisplayNode+UIViewBridge.mm | 3 +- .../Sample/AppDelegate.m | 5 + .../Sample/GradientTableNode.mm | 2 +- .../Sample/RandomCoreGraphicsNode.h | 3 + .../Sample/RandomCoreGraphicsNode.m | 31 ++++ 26 files changed, 382 insertions(+), 187 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ce7f290bf9..c622894086 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -459,6 +459,7 @@ D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE8BEAC11C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; @@ -469,6 +470,10 @@ DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; + DECC2ECD1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */ = {isa = PBXBuildFile; fileRef = DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */; }; + DECC2ECE1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */ = {isa = PBXBuildFile; fileRef = DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */; }; + DECC2ECF1C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */; }; + DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -762,6 +767,8 @@ DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; + DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerBeta.h; sourceTree = ""; }; + DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeControllerBeta.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -771,6 +778,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1029,6 +1037,8 @@ 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, + DECC2ECB1C35C1C600388446 /* ASRangeControllerBeta.h */, + DECC2ECC1C35C1C600388446 /* ASRangeControllerBeta.mm */, 292C599C1A956527007E5DD6 /* ASRangeHandler.h */, 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */, 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */, @@ -1315,6 +1325,7 @@ 9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */, 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */, 257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */, + DECC2ECD1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */, ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */, ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */, AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, @@ -1373,6 +1384,7 @@ B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, + DECC2ECE1C35C1C600388446 /* ASRangeControllerBeta.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, @@ -1753,6 +1765,7 @@ 257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */, 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, + DECC2ECF1C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, @@ -1883,6 +1896,7 @@ 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, + DECC2ED01C35C1C600388446 /* ASRangeControllerBeta.mm in Sources */, 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */, AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index cc6626f346..d6f6f70908 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -14,6 +14,7 @@ #import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import "ASDisplayNode+Beta.h" #import "ASInternalHelpers.h" #import "ASRangeController.h" #import "UICollectionViewLayout+ASConvenience.h" @@ -150,9 +151,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; self.strongCollectionNode = collectionNode; } - _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; + _layoutController = [ASDisplayNode shouldUseNewRenderingRange] ? + [[ASCollectionViewLayoutControllerBeta alloc] initWithCollectionView:self] : + [[ASCollectionViewLayoutControllerStable alloc] initWithCollectionView:self]; - _rangeController = [[ASRangeController alloc] init]; + _rangeController = [ASDisplayNode shouldUseNewRenderingRange] ? [[ASRangeControllerBeta alloc] init] + : [[ASRangeControllerStable alloc] init]; _rangeController.dataSource = self; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; @@ -319,22 +323,22 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - [_layoutController setTuningParameters:tuningParameters forRangeType:rangeType]; + [_rangeController setTuningParameters:tuningParameters forRangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [_layoutController tuningParametersForRangeType:rangeType]; + return [_rangeController tuningParametersForRangeType:rangeType]; } - (ASRangeTuningParameters)rangeTuningParameters { - return [self tuningParametersForRangeType:ASLayoutRangeTypeRender]; + return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay]; } - (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters { - [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; + [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; } - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath @@ -775,6 +779,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [_dataController nodesAtIndexPaths:indexPaths]; } +- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath +{ + return [_dataController nodeAtIndexPath:indexPath]; +} + #pragma mark - ASRangeControllerDelegate - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ba941a9065..97c2be98ed 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -203,20 +203,26 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssertMainThread(); static NSMutableSet *nodesToDisplay = nil; static BOOL displayScheduled = NO; - if (!nodesToDisplay) { - nodesToDisplay = [[NSMutableSet alloc] init]; + static ASDN::RecursiveMutex displaySchedulerLock; + { + ASDN::MutexLocker l(displaySchedulerLock); + if (!nodesToDisplay) { + nodesToDisplay = [[NSMutableSet alloc] init]; + } + [nodesToDisplay addObject:node]; } - [nodesToDisplay addObject:node]; if (!displayScheduled) { displayScheduled = YES; // It's essenital that any layout pass that is scheduled during the current // runloop has a chance to be applied / scheduled, so always perform this after the current runloop. dispatch_async(dispatch_get_main_queue(), ^{ + ASDN::MutexLocker l(displaySchedulerLock); displayScheduled = NO; - for (ASDisplayNode *node in nodesToDisplay) { + NSSet *displayingNodes = [nodesToDisplay copy]; + nodesToDisplay = nil; + for (ASDisplayNode *node in displayingNodes) { [node __recursivelyTriggerDisplayAndBlock:NO]; } - nodesToDisplay = nil; }); } } @@ -1835,6 +1841,15 @@ static BOOL ShouldUseNewRenderingRange = NO; }); } +- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState +{ + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.interfaceState = interfaceState; + }); + // FIXME: This should also be called in setInterfaceState: if it isn't being applied recursively. + [ASDisplayNode scheduleNodeForDisplay:self]; +} + - (ASHierarchyState)hierarchyState { ASDN::MutexLocker l(_propertyLock); diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 95d25f268c..a69a963d4d 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -9,6 +9,7 @@ #import "ASPagerNode.h" #import "ASDelegateProxy.h" #import "ASDisplayNode+Subclasses.h" +#import "UICollectionViewLayout+ASConvenience.h" @interface ASPagerNode () { @@ -34,7 +35,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout; { - ASDisplayNodeAssert([flowLayout isKindOfClass:[UICollectionViewFlowLayout class]], @"ASPagerNode requires a flow layout."); + ASDisplayNodeAssert([flowLayout asdk_isFlowLayout], @"ASPagerNode requires a flow layout."); self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { _flowLayout = flowLayout; @@ -61,8 +62,8 @@ ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - [self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypePreload]; - [self setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeRender]; + [self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypeFetchData]; + [self setTuningParameters:renderParams forRangeType:ASLayoutRangeTypeDisplay]; } #pragma mark - Helpers diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 71e0c93cbf..679a00d46a 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -13,6 +13,7 @@ #import "ASChangeSetDataController.h" #import "ASCollectionViewLayoutController.h" #import "ASDelegateProxy.h" +#import "ASDisplayNode+Beta.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -85,7 +86,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)_initWithTableView:(ASTableView *)tableView; @end -@interface ASTableView () { +@interface ASTableView () +{ ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -139,7 +143,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; - _rangeController = [[ASRangeController alloc] init]; + _rangeController = [ASDisplayNode shouldUseNewRenderingRange] ? [[ASRangeControllerBeta alloc] init] + : [[ASRangeControllerStable alloc] init]; _rangeController.layoutController = _layoutController; _rangeController.dataSource = self; _rangeController.delegate = self; @@ -317,12 +322,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (ASRangeTuningParameters)rangeTuningParameters { - return [self tuningParametersForRangeType:ASLayoutRangeTypeRender]; + return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay]; } - (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters { - [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; + [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; } - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath @@ -684,6 +689,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return [_dataController nodesAtIndexPaths:indexPaths]; } +- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath +{ + return [_dataController nodeAtIndexPath:indexPath]; +} + - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.h b/AsyncDisplayKit/Details/ASAbstractLayoutController.h index 153379fad6..fbe09de2e0 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.h +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.h @@ -13,10 +13,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ASAbstractLayoutController : NSObject -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; - @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 1aef1c1e0e..2a6678b1a9 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -14,6 +14,7 @@ @interface ASAbstractLayoutController () { std::vector _tuningParameters; + CGSize _viewportSize; } @end @@ -30,11 +31,11 @@ .leadingBufferScreenfuls = 0, .trailingBufferScreenfuls = 0 }; - _tuningParameters[ASLayoutRangeTypeRender] = { + _tuningParameters[ASLayoutRangeTypeDisplay] = { .leadingBufferScreenfuls = 1.5, .trailingBufferScreenfuls = 0.75 }; - _tuningParameters[ASLayoutRangeTypePreload] = { + _tuningParameters[ASLayoutRangeTypeFetchData] = { .leadingBufferScreenfuls = 3, .trailingBufferScreenfuls = 2 }; @@ -60,16 +61,27 @@ #pragma mark - Abstract Index Path Range Support -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssertNotSupported(); return NO; } -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssertNotSupported(); return nil; } +- (void)setViewportSize:(CGSize)viewportSize +{ + _viewportSize = viewportSize; +} + +- (CGSize)viewportSize +{ + return _viewportSize; +} + @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h index 632ba46bf8..3f14672dcf 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h @@ -19,4 +19,10 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASCollectionViewLayoutControllerStable : ASCollectionViewLayoutController +@end + +@interface ASCollectionViewLayoutControllerBeta : ASCollectionViewLayoutController +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm index 9619cba111..c959e86434 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -15,36 +15,6 @@ #import "CGRect+ASConvenience.h" #import "UICollectionViewLayout+ASConvenience.h" -struct ASDirectionalScreenfulBuffer { - CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. - CGFloat negativeDirection; -}; -typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters) -{ - ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; - BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); - horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls : - rangeTuningParameters.trailingBufferScreenfuls; - horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls : - rangeTuningParameters.leadingBufferScreenfuls; - return horizontalBuffer; -} - -ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, - ASRangeTuningParameters rangeTuningParameters) -{ - ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; - BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); - verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls : - rangeTuningParameters.trailingBufferScreenfuls; - verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls : - rangeTuningParameters.leadingBufferScreenfuls; - return verticalBuffer; -} - struct ASRangeGeometry { CGRect rangeBounds; CGRect updateBounds; @@ -57,9 +27,9 @@ typedef struct ASRangeGeometry ASRangeGeometry; @interface ASCollectionViewLayoutController () { + @package ASCollectionView * __weak _collectionView; UICollectionViewLayout * __strong _collectionViewLayout; - std::vector _updateRangeBoundsIndexedByRangeType; ASScrollDirection _scrollableDirections; } @end @@ -75,63 +45,34 @@ typedef struct ASRangeGeometry ASRangeGeometry; _scrollableDirections = [collectionView scrollableDirections]; _collectionView = collectionView; _collectionViewLayout = [collectionView collectionViewLayout]; + return self; +} + +@end + +@implementation ASCollectionViewLayoutControllerStable +{ + std::vector _updateRangeBoundsIndexedByRangeType; +} + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView +{ + if (!(self = [super initWithCollectionView:collectionView])) { + return nil; + } + _updateRangeBoundsIndexedByRangeType = std::vector(ASLayoutRangeTypeCount); return self; } -#pragma mark - -#pragma mark Index Paths in Range - -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection - viewportSize:(CGSize)viewportSize - rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType { - ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection - rangeTuningParameters:[self tuningParametersForRangeType:rangeType]]; + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType]; + ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection tuningParameters:tuningParameters]; _updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds; return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds]; } -- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection - rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters -{ - CGRect rangeBounds = _collectionView.bounds; - CGRect updateBounds = _collectionView.bounds; - - //scrollable directions can change for non-flow layouts - if ([_collectionViewLayout asdk_isFlowLayout] == NO) { - _scrollableDirections = [_collectionView scrollableDirections]; - } - - BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(_scrollableDirections); - if (canScrollHorizontally) { - ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, - rangeTuningParameters); - rangeBounds = asdk_CGRectExpandHorizontally(rangeBounds, - horizontalBuffer.negativeDirection, - horizontalBuffer.positiveDirection); - // Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value. - updateBounds = asdk_CGRectExpandHorizontally(updateBounds, - MIN(horizontalBuffer.negativeDirection * 0.5, 0.95), - MIN(horizontalBuffer.positiveDirection * 0.5, 0.95)); - } - - BOOL canScrollVertically = ASScrollDirectionContainsVerticalDirection(_scrollableDirections); - if (canScrollVertically) { - ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, - rangeTuningParameters); - rangeBounds = asdk_CGRectExpandVertically(rangeBounds, - verticalBuffer.negativeDirection, - verticalBuffer.positiveDirection); - // Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value. - updateBounds = asdk_CGRectExpandVertically(updateBounds, - MIN(verticalBuffer.negativeDirection * 0.5, 0.95), - MIN(verticalBuffer.positiveDirection * 0.5, 0.95)); - } - - return {rangeBounds, updateBounds}; -} - - (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds { NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; @@ -144,13 +85,31 @@ typedef struct ASRangeGeometry ASRangeGeometry; return indexPathSet; } -#pragma mark - -#pragma mark Should Update Range - -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths - viewportSize:(CGSize)viewportSize - rangeType:(ASLayoutRangeType)rangeType +- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection + tuningParameters:(ASRangeTuningParameters)tuningParameters { + CGRect rangeBounds = _collectionView.bounds; + CGRect updateBounds = _collectionView.bounds; + + // Scrollable directions can change for non-flow layouts + if ([_collectionViewLayout asdk_isFlowLayout] == NO) { + _scrollableDirections = [_collectionView scrollableDirections]; + } + + rangeBounds = CGRectExpandToRangeWithScrollableDirections(rangeBounds, tuningParameters, _scrollableDirections, scrollDirection); + + ASRangeTuningParameters updateTuningParameters = tuningParameters; + updateTuningParameters.leadingBufferScreenfuls = MIN(updateTuningParameters.leadingBufferScreenfuls * 0.5, 0.95); + updateTuningParameters.trailingBufferScreenfuls = MIN(updateTuningParameters.trailingBufferScreenfuls * 0.5, 0.95); + + updateBounds = CGRectExpandToRangeWithScrollableDirections(updateBounds, updateTuningParameters, _scrollableDirections, scrollDirection); + + return {rangeBounds, updateBounds}; +} + +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType +{ + CGSize viewportSize = [self viewportSize]; CGRect updateRangeBounds = _updateRangeBoundsIndexedByRangeType[rangeType]; if (CGRectIsEmpty(updateRangeBounds)) { return YES; @@ -169,3 +128,40 @@ typedef struct ASRangeGeometry ASRangeGeometry; } @end + + +@implementation ASCollectionViewLayoutControllerBeta + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType +{ + ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeType:rangeType]; + CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; + return [self indexPathsForItemsWithinRangeBounds:rangeBounds]; +} + +- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds +{ + NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; + NSMutableSet *indexPathSet = [NSMutableSet setWithCapacity:layoutAttributes.count]; + for (UICollectionViewLayoutAttributes *la in layoutAttributes) { + //ASDisplayNodeAssert(![indexPathSet containsObject:la.indexPath], @"Shouldn't already contain indexPath"); + ASDisplayNodeAssert(la.representedElementCategory != UICollectionElementCategoryDecorationView, @"UICollectionView decoration views are not supported by ASCollectionView"); + [indexPathSet addObject:la.indexPath]; + } + return indexPathSet; +} + +- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection + rangeTuningParameters:(ASRangeTuningParameters)tuningParameters +{ + CGRect rect = _collectionView.bounds; + + // Scrollable directions can change for non-flow layouts + if ([_collectionViewLayout asdk_isFlowLayout] == NO) { + _scrollableDirections = [_collectionView scrollableDirections]; + } + + return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, _scrollableDirections, scrollDirection); +} + +@end diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index 7fce4c636d..0442fb68a5 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -39,7 +39,8 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; #pragma mark - Visible Indices -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType { if (!indexPaths.count || rangeType >= _rangesByType.size()) { return NO; @@ -73,10 +74,11 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3; * IndexPath array for the element in the working range. */ -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType { CGFloat viewportScreenMetric; ASScrollDirection leadingDirection; + CGSize viewportSize = [self viewportSize]; if (_layoutDirection == ASFlowLayoutDirectionHorizontal) { ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || scrollDirection == ASScrollDirectionLeft || scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction"); diff --git a/AsyncDisplayKit/Details/ASLayoutController.h b/AsyncDisplayKit/Details/ASLayoutController.h index 22e4b12a04..674db701ee 100644 --- a/AsyncDisplayKit/Details/ASLayoutController.h +++ b/AsyncDisplayKit/Details/ASLayoutController.h @@ -28,11 +28,14 @@ typedef struct { * * Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls. */ +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; + - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; -- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType; +// FIXME: This method can be removed once ASRangeControllerBeta becomes the main version. +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths rangeType:(ASLayoutRangeType)rangeType; -- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType; +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeType:(ASLayoutRangeType)rangeType; @optional @@ -46,6 +49,9 @@ typedef struct { - (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths; +- (void)setViewportSize:(CGSize)viewportSize; +- (CGSize)viewportSize; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index a1e51b5ac6..d25817d962 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -10,7 +10,10 @@ typedef NS_ENUM(NSInteger, ASLayoutRangeType) { ASLayoutRangeTypeVisible = 0, - ASLayoutRangeTypeRender, - ASLayoutRangeTypePreload, + ASLayoutRangeTypeDisplay, + ASLayoutRangeTypeFetchData, ASLayoutRangeTypeCount }; + +#define ASLayoutRangeTypeRender ASLayoutRangeTypeDisplay +#define ASLayoutRangeTypePreload ASLayoutRangeTypeFetchData diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index 3e8e00a534..6ffc16377d 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -26,6 +26,11 @@ NS_ASSUME_NONNULL_BEGIN * This includes cancelling those asynchronous operations as cells fall outside of the working ranges. */ @interface ASRangeController : ASDealloc2MainObject +{ + id _layoutController; + __weak id _dataSource; + __weak id _delegate; +} /** * Notify the range controller that the visible range has been updated. @@ -46,6 +51,9 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node; +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType; + /** * An object that describes the layout behavior of the ranged component (table view, collection view, etc.) * @@ -66,6 +74,12 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASRangeControllerStable : ASRangeController +@end + +@interface ASRangeControllerBeta : ASRangeController +@end + /** * Data source for ASRangeController. * @@ -88,15 +102,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController; -/** - * Fetch nodes at specific index paths. - * - * @param rangeController Sender. - * - * @param indexPaths Index paths. - */ - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths; +- (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath; + @end /** diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 519c327f0f..92a8f4f066 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -15,34 +15,61 @@ #import "ASRangeHandlerRender.h" #import "ASRangeHandlerPreload.h" #import "ASInternalHelpers.h" +#import "ASLayoutController.h" +#import "ASLayoutRangeType.h" -@interface ASRangeController () { +@implementation ASRangeController + +- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection +{ +} + +- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node +{ +} + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType +{ + [_layoutController setTuningParameters:tuningParameters forRangeType:rangeType]; +} + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType +{ + return [_layoutController tuningParametersForRangeType:rangeType]; +} + +@end + +@interface ASRangeControllerStable () +{ BOOL _rangeIsValid; - + // keys should be ASLayoutRangeTypes and values NSSets containing NSIndexPaths NSMutableDictionary *_rangeTypeIndexPaths; NSDictionary *_rangeTypeHandlers; BOOL _queuedRangeUpdate; - + ASScrollDirection _scrollDirection; } @end -@implementation ASRangeController +@implementation ASRangeControllerStable -- (instancetype)init { - self = [super init]; - if (self != nil) { - _rangeIsValid = YES; - _rangeTypeIndexPaths = [NSMutableDictionary dictionary]; - _rangeTypeHandlers = @{ - @(ASLayoutRangeTypeVisible): [[ASRangeHandlerVisible alloc] init], - @(ASLayoutRangeTypeRender): [[ASRangeHandlerRender alloc] init], - @(ASLayoutRangeTypePreload): [[ASRangeHandlerPreload alloc] init], - }; +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; } - + + _rangeIsValid = YES; + _rangeTypeIndexPaths = [NSMutableDictionary dictionary]; + _rangeTypeHandlers = @{ + @(ASLayoutRangeTypeVisible) : [[ASRangeHandlerVisible alloc] init], + @(ASLayoutRangeTypeDisplay) : [[ASRangeHandlerRender alloc] init], + @(ASLayoutRangeTypeFetchData): [[ASRangeHandlerPreload alloc] init], + }; + return self; } @@ -111,12 +138,13 @@ NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; CGSize viewportSize = [_dataSource viewportSizeForRangeController:self]; + [_layoutController setViewportSize:viewportSize]; // the layout controller needs to know what the current visible indices are to calculate range offsets if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) { [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; } - + for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { ASLayoutRangeType rangeType = (ASLayoutRangeType)i; id rangeKey = @(rangeType); @@ -124,10 +152,8 @@ // this delegate decide what happens when a node is added or removed from a range id rangeHandler = _rangeTypeHandlers[rangeKey]; - if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths viewportSize:viewportSize rangeType:rangeType]) { - NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - viewportSize:viewportSize - rangeType:rangeType]; + if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths rangeType:rangeType]) { + NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:rangeType]; // Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths // This value may be nil for the first call of this method. @@ -172,7 +198,7 @@ - (BOOL)shouldSkipVisibleNodesForRangeType:(ASLayoutRangeType)rangeType { - return rangeType == ASLayoutRangeTypeRender; + return rangeType == ASLayoutRangeTypeDisplay; } #pragma mark - ASDataControllerDelegete diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 40861a0fe4..3160264c1d 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -72,26 +72,19 @@ } CGSize viewportSize = [_dataSource viewportSizeForRangeController:self]; + [_layoutController setViewportSize:viewportSize]; // the layout controller needs to know what the current visible indices are to calculate range offsets if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) { [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; } - NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - viewportSize:viewportSize - rangeType:ASLayoutRangeTypeFetchData]; - - NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - viewportSize:viewportSize - rangeType:ASLayoutRangeTypeDisplay]; - - NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection - viewportSize:viewportSize - rangeType:ASLayoutRangeTypeVisible]; + NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData]; + NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay]; + NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; - NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; -// NSLog(@"visible sets are equal: %d", [visibleIndexPaths isEqualToSet:visibleNodePathsSet]); + //NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; + //NSLog(@"visible sets are equal: %d", [visibleIndexPaths isEqualToSet:visibleNodePathsSet]); // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. NSMutableSet *allIndexPaths = [fetchDataIndexPaths mutableCopy]; diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm index 09a3623a26..3eccec5dd8 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm @@ -14,13 +14,13 @@ - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges"); + ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeFetchData, @"Preload delegate should not handle other ranges"); [node enterInterfaceState:ASInterfaceStateFetchData]; } - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType { - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges"); + ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeFetchData, @"Preload delegate should not handle other ranges"); [node exitInterfaceState:ASInterfaceStateFetchData]; } diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index af05d29cc0..52bc4e604d 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -44,7 +44,7 @@ if (![ASDisplayNode shouldUseNewRenderingRange]) { for (CALayer *layer in [self.workingWindow.layer.sublayers copy]) { ASDisplayNode *node = layer.asyncdisplaykit_node; - [self node:node exitedRangeOfType:ASLayoutRangeTypeRender]; + [self node:node exitedRangeOfType:ASLayoutRangeTypeDisplay]; } } } @@ -52,7 +52,7 @@ - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeRender, @"Render delegate should not handle other ranges"); + ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeDisplay, @"Render delegate should not handle other ranges"); // If a node had previously been onscreen but now is only in the working range, // ensure its view is not orphaned in a UITableViewCell in the reuse pool. @@ -64,6 +64,7 @@ [node enterInterfaceState:ASInterfaceStateDisplay]; + ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled"); if ([ASDisplayNode shouldUseNewRenderingRange]) { [node recursivelyEnsureDisplaySynchronously:NO]; } else { @@ -79,7 +80,7 @@ - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeRender, @"Render delegate should not handle other ranges"); + ASDisplayNodeAssert(rangeType == ASLayoutRangeTypeDisplay, @"Render delegate should not handle other ranges"); // This code is tricky. There are several possible states a node can be in when it reaches this point. // 1. Layer-backed vs view-backed nodes. AS of this writing, only ASCellNodes arrive here, which are always view-backed — @@ -101,6 +102,8 @@ // The node calls clearCurrentContents and suspends display [node exitInterfaceState:ASInterfaceStateDisplay]; + ASDisplayNodeAssert(![ASDisplayNode shouldUseNewRenderingRange], @"It should no longer be possible to reach this point with the new display range enabled"); + if ([ASDisplayNode shouldUseNewRenderingRange]) { if (![node isLayerBacked]) { [node.view removeFromSuperview]; diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.h b/AsyncDisplayKit/Details/CGRect+ASConvenience.h index a60b46be00..1d672b5f15 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.h +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.h @@ -10,13 +10,16 @@ #import #import "ASBaseDefines.h" +#import "ASLayoutController.h" NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN -CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier); -CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier); +CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, + ASRangeTuningParameters tuningParameters, + ASScrollDirection scrollableDirections, + ASScrollDirection scrollDirection); ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.m b/AsyncDisplayKit/Details/CGRect+ASConvenience.m index 171f3d3986..312bac61f2 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.m +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.m @@ -7,25 +7,74 @@ */ #import "CGRect+ASConvenience.h" +#import "ASScrollDirection.h" +#import "ASLayoutController.h" -CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) { - CGFloat negativeDirectionWidth = negativeMultiplier * rect.size.width; - CGFloat positiveDirectionWidth = positiveMultiplier * rect.size.width; - CGFloat width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; - CGFloat originX = rect.origin.x - negativeDirectionWidth; - return CGRectMake(originX, - rect.origin.y, - width, - rect.size.height); +struct ASDirectionalScreenfulBuffer { + CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. + CGFloat negativeDirection; +}; +typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; + +ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) +{ + ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; + BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); + + horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls + : rangeTuningParameters.trailingBufferScreenfuls; + horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls + : rangeTuningParameters.leadingBufferScreenfuls; + return horizontalBuffer; } -CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) { - CGFloat negativeDirectionHeight = negativeMultiplier * rect.size.height; - CGFloat positiveDirectionHeight = positiveMultiplier * rect.size.height; - CGFloat height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; - CGFloat originY = rect.origin.y - negativeDirectionHeight; - return CGRectMake(rect.origin.x, - originY, - rect.size.width, - height); +ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) +{ + ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; + BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); + + verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls + : rangeTuningParameters.trailingBufferScreenfuls; + verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls + : rangeTuningParameters.leadingBufferScreenfuls; + return verticalBuffer; } + +CGRect CGRectExpandHorizontally(CGRect rect, ASDirectionalScreenfulBuffer buffer) +{ + CGFloat negativeDirectionWidth = buffer.negativeDirection * rect.size.width; + CGFloat positiveDirectionWidth = buffer.positiveDirection * rect.size.width; + rect.size.width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; + rect.origin.x -= negativeDirectionWidth; + return rect; +} + +CGRect CGRectExpandVertically(CGRect rect, ASDirectionalScreenfulBuffer buffer) +{ + CGFloat negativeDirectionHeight = buffer.negativeDirection * rect.size.height; + CGFloat positiveDirectionHeight = buffer.positiveDirection * rect.size.height; + rect.size.height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; + rect.origin.y -= negativeDirectionHeight; + return rect; +} + +CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTuningParameters tuningParameters, + ASScrollDirection scrollableDirections, ASScrollDirection scrollDirection) +{ + // Can scroll horizontally - expand the range appropriately + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { + ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); + rect = CGRectExpandHorizontally(rect, horizontalBuffer); + } + + // Can scroll vertically - expand the range appropriately + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { + ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); + rect = CGRectExpandVertically(rect, verticalBuffer); + } + + return rect; +} + diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m index a401ec549e..9098a96349 100644 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m @@ -10,8 +10,9 @@ @implementation UICollectionViewLayout (ASConvenience) -- (BOOL)asdk_isFlowLayout { - return [self isKindOfClass:UICollectionViewFlowLayout.class]; +- (BOOL)asdk_isFlowLayout +{ + return [self isKindOfClass:[UICollectionViewFlowLayout class]]; } @end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 8deebb9bc6..896e82668f 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -60,6 +60,7 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) // These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; - (void)exitInterfaceState:(ASInterfaceState)interfaceState; +- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState; // These methods are recursive, and either union or remove the provided hierarchyState to all sub-elements. - (void)enterHierarchyState:(ASHierarchyState)hierarchyState; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 1b4dd81788..00fdf6bdde 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -250,7 +250,8 @@ _messageToViewOrLayer(setNeedsDisplay); if ([ASDisplayNode shouldUseNewRenderingRange]) { - if (_layer && !self.isSynchronous) { + BOOL shouldDisplay = ((_interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay); + if (_layer && !_flags.synchronous && shouldDisplay) { [ASDisplayNode scheduleNodeForDisplay:self]; } } diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m b/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m index 1dea563b77..3ba9d1faf9 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/AppDelegate.m @@ -13,10 +13,15 @@ #import "ViewController.h" +#import +#import + @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [ASDisplayNode setShouldUseNewRenderingRange:YES]; + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm b/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm index 6d2c5c3d6b..2721a92d15 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm +++ b/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm @@ -51,7 +51,7 @@ ASRangeTuningParameters rangeTuningParameters; rangeTuningParameters.leadingBufferScreenfuls = 1.0; rangeTuningParameters.trailingBufferScreenfuls = 0.5; - [_tableNode.view setTuningParameters:rangeTuningParameters forRangeType:ASLayoutRangeTypeRender]; + [_tableNode.view setTuningParameters:rangeTuningParameters forRangeType:ASLayoutRangeTypeDisplay]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h index 4e111ad003..09e2d201d8 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h +++ b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h @@ -9,6 +9,9 @@ #import @interface RandomCoreGraphicsNode : ASCellNode +{ + ASTextNode *_indexPathTextNode; +} @property (nonatomic) NSIndexPath *indexPath; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m index b9fb37be37..18f71821cd 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m @@ -42,6 +42,37 @@ CGColorSpaceRelease(colorSpace); } +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _indexPathTextNode = [[ASTextNode alloc] init]; + [self addSubnode:_indexPathTextNode]; + + return self; +} + +- (void)setIndexPath:(NSIndexPath *)indexPath +{ + _indexPath = indexPath; + _indexPathTextNode.attributedString = [[NSAttributedString alloc] initWithString:[indexPath description] attributes:nil]; +} + +//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +//{ +// ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStart children:@[_indexPathTextNode]]; +// stackSpec.flexGrow = YES; +// return stackSpec; +//} + +- (void)layout +{ + _indexPathTextNode.frame = self.bounds; + [super layout]; +} + #if 0 - (void)fetchData {