From ffeb5148024240d874b1240c022d2e40ab757ed6 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 26 Dec 2015 13:35:48 -0800 Subject: [PATCH 01/39] Disable "intrument program flow" to fix error spew after running tests. Enable Xcode 7 UI for code coverage. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 14 ++++++++------ .../xcschemes/AsyncDisplayKit.xcscheme | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3a920cc5b5..ce7f290bf9 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1934,7 +1934,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1955,7 +1955,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 7.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -2041,9 +2041,10 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_CODE_COVERAGE = YES; DSTROOT = /tmp/AsyncDisplayKit.dst; GCC_INPUT_FILETYPE = automatic; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -2060,9 +2061,10 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_CODE_COVERAGE = YES; DSTROOT = /tmp/AsyncDisplayKit.dst; GCC_INPUT_FILETYPE = automatic; - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; @@ -2085,7 +2087,7 @@ "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -2113,7 +2115,7 @@ "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); - GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index b871e34397..07bd75cea5 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -40,7 +40,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> From a1429ea23b91cd1a33cc50d5dad23fe2428c06d8 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 26 Dec 2015 14:22:24 -0800 Subject: [PATCH 02/39] Add ability to enable new rendering range with a class method in ASDisplayNode+Beta.h --- AsyncDisplayKit/ASDisplayNode+Beta.h | 3 + AsyncDisplayKit/ASDisplayNode.mm | 11 ++++ .../Details/ASRangeHandlerRender.mm | 61 ++++++++++--------- .../Private/ASDisplayNode+FrameworkPrivate.h | 4 -- .../Private/ASDisplayNode+UIViewBridge.mm | 9 +-- 5 files changed, 50 insertions(+), 38 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 608f7be71b..98efc3d08a 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -8,6 +8,9 @@ @interface ASDisplayNode (Beta) ++ (BOOL)shouldUseNewRenderingRange; ++ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange; + /** @name Layout */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 19c972f790..8d0089ce8e 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1560,6 +1560,17 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return _flags.shouldBypassEnsureDisplay; } +static BOOL ShouldUseNewRenderingRange = NO; + ++ (BOOL)shouldUseNewRenderingRange +{ + return ShouldUseNewRenderingRange; +} ++ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange +{ + ShouldUseNewRenderingRange = shouldUseNewRenderingRange; +} + #pragma mark - For Subclasses - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index 4872707e7e..bb4de3c092 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -11,15 +11,15 @@ #import "ASDisplayNode.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import "ASDisplayNode+Beta.h" @interface ASRangeHandlerRender () @property (nonatomic,readonly) UIWindow *workingWindow; @end @implementation ASRangeHandlerRender - -#if USE_WORKING_WINDOW @synthesize workingWindow = _workingWindow; + - (UIWindow *)workingWindow { ASDisplayNodeAssertMainThread(); @@ -28,7 +28,7 @@ // TODO: Replace this with directly triggering display https://github.com/facebook/AsyncDisplayKit/issues/315 // Update: Latest attempt is at https://github.com/facebook/AsyncDisplayKit/pull/828 - if (!_workingWindow) { + if (!_workingWindow && ![ASDisplayNode shouldUseNewRenderingRange]) { _workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero]; _workingWindow.windowLevel = UIWindowLevelNormal - 1000; _workingWindow.userInteractionEnabled = NO; @@ -41,12 +41,13 @@ - (void)dealloc { - for(CALayer *layer in [self.workingWindow.layer.sublayers copy]) { - ASDisplayNode *node = layer.asyncdisplaykit_node; - [self node:node exitedRangeOfType:ASLayoutRangeTypeRender]; + if (![ASDisplayNode shouldUseNewRenderingRange]) { + for (CALayer *layer in [self.workingWindow.layer.sublayers copy]) { + ASDisplayNode *node = layer.asyncdisplaykit_node; + [self node:node exitedRangeOfType:ASLayoutRangeTypeRender]; + } } } -#endif - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { @@ -63,16 +64,16 @@ [node enterInterfaceState:ASInterfaceStateDisplay]; -#if USE_WORKING_WINDOW - // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. - // Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations. - // Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will - // also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view - // being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc) - [[[self workingWindow] layer] addSublayer:node.layer]; -#else - [node recursivelyEnsureDisplay]; // Need to do this without waiting -#endif + if (![ASDisplayNode shouldUseNewRenderingRange]) { + [node recursivelyEnsureDisplay]; // Need to do this without waiting + } else { + // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. + // Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations. + // Any view-backed nodes will still create their views in order to assemble the layer heirarchy, and they will + // also assemble a view subtree for the node, but we avoid the much more significant expense triggered by a view + // being added or removed from an onscreen window (responder chain setup, will/DidMoveToWindow: recursive calls, etc) + [[[self workingWindow] layer] addSublayer:node.layer]; + } } - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType @@ -100,25 +101,25 @@ // The node calls clearCurrentContents and suspends display [node exitInterfaceState:ASInterfaceStateDisplay]; -#if USE_WORKING_WINDOW - if (node.layer.superlayer != [[self workingWindow] layer]) { - // In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range. + if (![ASDisplayNode shouldUseNewRenderingRange]) { if (![node isLayerBacked]) { - // If the node is view-backed, we need to make sure to remove the view (which is now present in the containing cell contentsView). - // Layer-backed nodes will be fully handled by the unconditional removal below. [node.view removeFromSuperview]; + } else { + [node.layer removeFromSuperlayer]; } - } - - // At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell. - [node.layer removeFromSuperlayer]; -#else - if (![node isLayerBacked]) { - [node.view removeFromSuperview]; } else { + if (node.layer.superlayer != [[self workingWindow] layer]) { + // In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range. + if (![node isLayerBacked]) { + // If the node is view-backed, we need to make sure to remove the view (which is now present in the containing cell contentsView). + // Layer-backed nodes will be fully handled by the unconditional removal below. + [node.view removeFromSuperview]; + } + } + + // At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell. [node.layer removeFromSuperlayer]; } -#endif } @end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 0c016d009c..5834bc3b9a 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -20,10 +20,6 @@ NS_ASSUME_NONNULL_BEGIN -// Project-wide control for whether the offscreen UIWindow is used for display, or if -// ASDK's internal system for coalescing and triggering display events is used. -#define USE_WORKING_WINDOW 1 - /** Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree. Examples include rasterization and external driving of the .interfaceState property. diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index d14c042c0b..1b4dd81788 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -13,6 +13,7 @@ #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import "ASDisplayNode+Beta.h" #import "ASEqualityHelpers.h" /** @@ -248,11 +249,11 @@ _messageToViewOrLayer(setNeedsDisplay); -#if !USE_WORKING_WINDOW - if (_layer && !self.isSynchronous) { - [ASDisplayNode scheduleNodeForDisplay:self]; + if ([ASDisplayNode shouldUseNewRenderingRange]) { + if (_layer && !self.isSynchronous) { + [ASDisplayNode scheduleNodeForDisplay:self]; + } } -#endif } } From 4b560a703ac1c1d85076c9a0eb45bca21fbcf50d Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 26 Dec 2015 16:34:44 -0800 Subject: [PATCH 03/39] PR #1000 on GitHub! Provide a Beta API to enable the new, high efficiency render-ahead mechanism. --- AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/ASDisplayNode+Beta.h | 3 ++- AsyncDisplayKit/ASDisplayNode.mm | 4 ++-- AsyncDisplayKit/ASTableView.mm | 2 +- AsyncDisplayKit/ASViewController.m | 2 +- AsyncDisplayKit/Details/ASRangeHandlerRender.mm | 2 +- AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h | 2 +- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 8686ce6cca..7c2bcfa42d 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -527,7 +527,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]; if (cellNode.neverShowPlaceholders) { - [cellNode recursivelyEnsureDisplay]; + [cellNode recursivelyEnsureDisplaySynchronously:YES]; } } diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 98efc3d08a..473b565f27 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -16,7 +16,8 @@ /** * @abstract Recursively ensures node and all subnodes are displayed. + * @see Full documentation in ASDisplayNode+FrameworkPrivate.h */ -- (void)recursivelyEnsureDisplay; +- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously; @end diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 8d0089ce8e..50bd7e5e12 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1543,9 +1543,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) recursivelyTriggerDisplayForLayer(layer, shouldBlock); } -- (void)recursivelyEnsureDisplay +- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously { - [self __recursivelyTriggerDisplayAndBlock:YES]; + [self __recursivelyTriggerDisplayAndBlock:synchronously]; } - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index b2f6457f3d..b3e423a030 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -558,7 +558,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASCellNode *cellNode = [self nodeForRowAtIndexPath:indexPath]; if (cellNode.neverShowPlaceholders) { - [cellNode recursivelyEnsureDisplay]; + [cellNode recursivelyEnsureDisplaySynchronously:YES]; } } diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 9679785558..b626c7169c 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -57,7 +57,7 @@ { if (_ensureDisplayed && self.neverShowPlaceholders) { _ensureDisplayed = NO; - [self.node recursivelyEnsureDisplay]; + [self.node recursivelyEnsureDisplaySynchronously:YES]; } [super viewDidLayoutSubviews]; } diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index bb4de3c092..6dcf11044d 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -65,7 +65,7 @@ if (![ASDisplayNode shouldUseNewRenderingRange]) { - [node recursivelyEnsureDisplay]; // Need to do this without waiting + [node recursivelyEnsureDisplaySynchronously:NO]; } else { // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. // Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations. diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 5834bc3b9a..8deebb9bc6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -90,7 +90,7 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) * In order to guarantee against deadlocks, this method should only be called on the main thread. * It may block on the private queue, [_ASDisplayLayer displayQueue] */ -- (void)recursivelyEnsureDisplay; +- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously; /** * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. From 44feece701e964c95f9ea0f958d8171cd2bb5f86 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 26 Dec 2015 23:04:16 -0800 Subject: [PATCH 04/39] Implement node-backing for ASTableView and ASCollectionView, with a strong back-pointer in these cases. --- AsyncDisplayKit/ASCollectionNode.m | 13 +++++- AsyncDisplayKit/ASCollectionView.h | 12 ++++-- AsyncDisplayKit/ASCollectionView.mm | 25 +++++++++--- AsyncDisplayKit/ASDisplayNode.h | 1 + AsyncDisplayKit/ASTableNode.m | 25 ++++++++---- AsyncDisplayKit/ASTableView.h | 12 ++++-- AsyncDisplayKit/ASTableView.mm | 40 +++++++++++-------- AsyncDisplayKit/ASTableViewInternal.h | 2 +- AsyncDisplayKit/Layout/ASLayoutOptions.h | 2 +- AsyncDisplayKit/Layout/ASLayoutOptions.mm | 4 +- .../Private/ASDisplayNodeInternal.h | 14 +++++++ AsyncDisplayKitTests/ASTableViewTests.m | 36 +++++++---------- 12 files changed, 124 insertions(+), 62 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index 1b8484b777..d75d4ad58a 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -22,7 +22,7 @@ @end @interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ownedByNode:(BOOL)ownedByNode; @end @implementation ASCollectionNode @@ -40,10 +40,19 @@ return [self initWithFrame:CGRectZero collectionViewLayout:layout]; } +- (instancetype)_initWithCollectionView:(ASCollectionView *)collectionView +{ + if (self = [super initWithViewBlock:^UIView *{ return collectionView; }]) { + __unused ASCollectionView *collectionView = [self view]; + return self; + } + return nil; +} + - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ - return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout]; + return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout ownedByNode:YES]; }; if (self = [super initWithViewBlock:collectionViewBlock]) { diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 55b3afafb1..fb818eb7e6 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -22,10 +22,16 @@ NS_ASSUME_NONNULL_BEGIN /** - * Node-based collection view. + * Asynchronous UICollectionView with Intelligent Preloading capabilities. * - * ASCollectionView is a version of UICollectionView that uses nodes -- specifically, ASCellNode subclasses -- with asynchronous - * pre-rendering instead of synchronously loading UICollectionViewCells. + * ASCollectionNode is recommended over ASCollectionView. This class exists for adoption convenience. + * + * ASCollectionView is a true subclass of UICollectionView, meaning it is pointer-compatible + * with code that currently uses UICollectionView. + * + * The main difference is that asyncDataSource expects -nodeForItemAtIndexPath, an ASCellNode, and + * the sizeForItemAtIndexPath: method is eliminated (as are the performance problems caused by it). + * This is made possible because ASCellNodes can calculate their own size, and preload ahead of time. */ @interface ASCollectionView : UICollectionView diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 7c2bcfa42d..2e7c17c017 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -99,6 +99,16 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; @property (atomic, assign) BOOL asyncDataSourceLocked; +// Used only when ASCollectionView is created directly rather than through ASCollectionNode. +// We create a node so that logic related to appearance, memory management, etc can be located there +// for both the node-based and view-based version of the table. +// This also permits sharing logic with ASTableNode, as the superclass is not UIKit-controlled. +@property (nonatomic, retain) ASCollectionNode *strongCollectionNode; + +@end + +@interface ASCollectionNode () +- (instancetype)_initWithCollectionView:(ASCollectionView *)collectionView; @end @implementation ASCollectionView @@ -108,26 +118,31 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { - return [self initWithFrame:CGRectZero collectionViewLayout:layout]; + return [self _initWithFrame:CGRectZero collectionViewLayout:layout ownedByNode:NO]; } - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { - ASCollectionNode *collectionNode = [[ASCollectionNode alloc] initWithFrame:frame collectionViewLayout:layout]; - return collectionNode.view; + return [self _initWithFrame:frame collectionViewLayout:layout ownedByNode:NO]; } // FIXME: This method is deprecated and will probably be removed in or shortly after 2.0. - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled { - return [self initWithFrame:frame collectionViewLayout:layout]; + return [self _initWithFrame:frame collectionViewLayout:layout ownedByNode:NO]; } -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ownedByNode:(BOOL)ownedByNode { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; + if (!ownedByNode) { + // See commentary at the definition of .strongCollectionNode for why we create an ASCollectionNode. + ASCollectionNode *collectionNode = [[ASCollectionNode alloc] _initWithCollectionView:self]; + self.strongCollectionNode = collectionNode; + } + _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; _rangeController = [[ASRangeController alloc] init]; diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 84d29f0f81..3e2a32fb44 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -205,6 +205,7 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) ASInterfaceState interfaceState; + /** @name Managing dimensions */ /** diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 2917b73cc8..e81366ed99 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -7,7 +7,7 @@ // #import "ASFlowLayoutController.h" -#import "ASTableNode.h" +#import "ASTableViewInternal.h" #import "ASDisplayNode+Subclasses.h" @interface _ASTablePendingState : NSObject @@ -28,11 +28,22 @@ @implementation ASTableNode -- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass +- (instancetype)_initWithTableView:(ASTableView *)tableView { - if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero - style:style - dataControllerClass:dataControllerClass]; }]) { + if (self = [super initWithViewBlock:^UIView *{ return tableView; }]) { + __unused ASTableView *tableView = [self view]; + return self; + } + return nil; +} + +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass +{ + ASDisplayNodeViewBlock tableViewBlock = ^UIView *{ + return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass ownedByNode:YES]; + }; + + if (self = [super initWithViewBlock:tableViewBlock]) { return self; } return nil; @@ -40,12 +51,12 @@ - (instancetype)initWithStyle:(UITableViewStyle)style { - return [self _initWithStyle:style dataControllerClass:nil]; + return [self _initWithFrame:CGRectZero style:style dataControllerClass:nil]; } - (instancetype)init { - return [self _initWithStyle:UITableViewStylePlain dataControllerClass:nil]; + return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil]; } - (void)didLoad diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index d3e2b05b51..f68606c66e 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -19,10 +19,16 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASTableDelegate; /** - * Node-based table view. + * Asynchronous UITableView with Intelligent Preloading capabilities. * - * ASTableView is a version of UITableView that uses nodes -- specifically, ASCellNode subclasses -- with asynchronous - * pre-rendering instead of synchronously loading UITableViewCells. + * ASTableNode is recommended over ASTableView. This class is provided for adoption convenience. + * + * ASTableView is a true subclass of UITableView, meaning it is pointer-compatible with code that + * currently uses UITableView + * + * The main difference is that asyncDataSource expects -nodeForRowAtIndexPath, an ASCellNode, and + * the heightForRowAtIndexPath: method is eliminated (as are the performance problems caused by it). + * This is made possible because ASCellNodes can calculate their own size, and preload ahead of time. */ @interface ASTableView : UITableView diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index b3e423a030..36c990b993 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -81,7 +81,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark ASTableView @interface ASTableNode () -- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; +- (instancetype)_initWithTableView:(ASTableView *)tableView; @end @interface ASTableView () { @@ -110,6 +110,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; @property (atomic, assign) BOOL asyncDataSourceLocked; @property (nonatomic, retain, readwrite) ASDataController *dataController; +// Used only when ASTableView is created directly rather than through ASTableNode. +// We create a node so that logic related to appearance, memory management, etc can be located there +// for both the node-based and view-based version of the table. +// This also permits sharing logic with ASCollectionNode, as the superclass is not UIKit-controlled. +@property (nonatomic, retain) ASTableNode *strongTableNode; + @end @implementation ASTableView @@ -122,7 +128,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark Lifecycle -- (void)configureWithDataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetching +- (void)configureWithDataControllerClass:(Class)dataControllerClass { _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; @@ -131,13 +137,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _rangeController.dataSource = self; _rangeController.delegate = self; - _dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:asyncDataFetching]; + _dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO]; _dataController.dataSource = self; _dataController.delegate = _rangeController; _layoutController.dataSource = _dataController; - _asyncDataFetchingEnabled = asyncDataFetching; + _asyncDataFetchingEnabled = NO; _asyncDataSourceLocked = NO; _leadingScreensForBatching = 1.0; @@ -161,32 +167,32 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { - return [self initWithFrame:frame style:style asyncDataFetching:NO]; + return [self _initWithFrame:frame style:style dataControllerClass:nil ownedByNode:NO]; } // FIXME: This method is deprecated and will probably be removed in or shortly after 2.0. - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled { - return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled]; + return [self _initWithFrame:frame style:style dataControllerClass:nil ownedByNode:NO]; } -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode { -// ASTableNode *tableNode = [[ASTableNode alloc] _initWithStyle:style dataControllerClass:dataControllerClass]; -// tableNode.frame = frame; -// return tableNode.view; - return [self _initWithFrame:frame style:style dataControllerClass:dataControllerClass]; -} - -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass -{ - if (!(self = [super initWithFrame:frame style:style])) + if (!(self = [super initWithFrame:frame style:style])) { return nil; + } if (!dataControllerClass) { dataControllerClass = [self.class dataControllerClass]; } - [self configureWithDataControllerClass:dataControllerClass asyncDataFetching:NO]; + + [self configureWithDataControllerClass:dataControllerClass]; + + if (!ownedByNode) { + // See commentary at the definition of .strongTableNode for why we create an ASTableNode. + ASTableNode *tableNode = [[ASTableNode alloc] _initWithTableView:self]; + self.strongTableNode = tableNode; + } return self; } diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h index 8d43beafb6..02eb062d46 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -26,6 +26,6 @@ * * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled; +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode; @end diff --git a/AsyncDisplayKit/Layout/ASLayoutOptions.h b/AsyncDisplayKit/Layout/ASLayoutOptions.h index 88036d1b49..7ce0f404e2 100644 --- a/AsyncDisplayKit/Layout/ASLayoutOptions.h +++ b/AsyncDisplayKit/Layout/ASLayoutOptions.h @@ -55,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return a new instance of ASLayoutOptions */ -- (instancetype)initWithLayoutable:(nullable id)layoutable NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithLayoutable:(id)layoutable; /** * Copies the values of layoutOptions into self. This is useful when placing a layoutable inside of another. Consider diff --git a/AsyncDisplayKit/Layout/ASLayoutOptions.mm b/AsyncDisplayKit/Layout/ASLayoutOptions.mm index a40eea2bd5..ad503014bf 100644 --- a/AsyncDisplayKit/Layout/ASLayoutOptions.mm +++ b/AsyncDisplayKit/Layout/ASLayoutOptions.mm @@ -56,10 +56,10 @@ static Class gDefaultLayoutOptionsClass = nil; - (instancetype)init { - return [self initWithLayoutable:nil]; + return nil; } -- (instancetype)initWithLayoutable:(id)layoutable; +- (instancetype)initWithLayoutable:(id)layoutable { self = [super init]; if (self) { diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index ca5a7ee1ca..1636e5a175 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -160,6 +160,20 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) @property (nonatomic, assign) CGFloat contentsScaleForDisplay; +/** + * // TODO: NOT YET IMPLEMENTED + * + * @abstract Prevents interface state changes from affecting the node, until disabled. + * + * @discussion Useful to avoid flashing after removing a node from the hierarchy and re-adding it. + * Removing a node from the hierarchy will cause it to exit the Display state, clearing its contents. + * For some animations, it's desirable to be able to remove a node without causing it to re-display. + * Once re-enabled, the interface state will be updated to the same value it would have been. + * + * @see ASInterfaceState + */ +@property (nonatomic, assign) BOOL interfaceStateSuspended; + /** * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, * but it's considered private API for now and its use should not be encouraged. diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index a382b6be8c..676634e106 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -37,9 +37,9 @@ @implementation ASTestTableView -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (instancetype)__initWithFrame:(CGRect)frame style:(UITableViewStyle)style { - return [super initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] asyncDataFetching:asyncDataFetchingEnabled]; + return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] ownedByNode:NO]; } - (ASTestDataController *)testDataController @@ -124,6 +124,7 @@ @end @interface ASTableViewTests : XCTestCase +@property (atomic, retain) ASTableView *testTableView; @end @implementation ASTableViewTests @@ -131,7 +132,7 @@ // TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero style:UITableViewStylePlain]; __block BOOL tableViewDidDealloc = NO; tableView.willDeallocBlock = ^(ASTableView *v){ @@ -185,9 +186,8 @@ - (void)testReloadData { // Keep the viewport moderately sized so that new cells are loaded on scrolling - ASTableView *tableView = [[ASTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 500) - style:UITableViewStylePlain - asyncDataFetching:YES]; + ASTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 500) + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; @@ -250,9 +250,8 @@ // Any subsequence size change must trigger a relayout. CGSize tableViewFinalSize = CGSizeMake(100, 500); // Width and height are swapped so that a later size change will simulate a rotation - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width) - style:UITableViewStylePlain - asyncDataFetching:YES]; + ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width) + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; @@ -270,9 +269,8 @@ // Initial width of the table view is 0. The first size change is part of the initial config. // Any subsequence size change after that must trigger a relayout. CGSize tableViewFinalSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero - style:UITableViewStylePlain - asyncDataFetching:YES]; + ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource; @@ -292,9 +290,8 @@ - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged { CGSize tableViewSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain - asyncDataFetching:YES]; + ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource; @@ -361,9 +358,8 @@ - (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible { CGSize tableViewSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain - asyncDataFetching:YES]; + ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource; @@ -398,9 +394,6 @@ style:UITableViewStylePlain asyncDataFetching:YES]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; -#if ! __has_feature(objc_arc) -#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif tableView.asyncDelegate = dataSource; tableView.asyncDataSource = dataSource; @@ -414,6 +407,7 @@ XCTAssertEqual(indexPath.row, reportedIndexPath.row); } } + self.testTableView = nil; }]; } From 690f90a899573ab89d8722ef86e7d40f4b4ace6c Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 27 Dec 2015 15:34:26 -0800 Subject: [PATCH 05/39] Supplementary nodes must be added to the completed nodes after their measurement completes following individual section reloads. --- .../Details/ASCollectionDataController.mm | 21 +++++++++++++++---- .../Sample/ViewController.m | 6 ++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index e8028161d6..9e51ee6a24 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -10,7 +10,7 @@ #import "ASAssert.h" #import "ASMultidimensionalArrayUtils.h" -#import "ASDisplayNode.h" +#import "ASCellNode.h" #import "ASDisplayNodeInternal.h" #import "ASDataController+Subclasses.h" @@ -142,7 +142,9 @@ NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + }]; [_pendingNodes removeObjectForKey:kind]; [_pendingIndexPaths removeObjectForKey:kind]; }]; @@ -187,7 +189,8 @@ for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - [nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]]; + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + [nodes addObject:supplementaryNode]; } }]; } @@ -208,7 +211,17 @@ - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return [self completedNodesOfKind:kind][indexPath.section][indexPath.item]; + NSArray *nodesOfKind = [self completedNodesOfKind:kind]; + NSInteger section = indexPath.section; + if (section < nodesOfKind.count) { + NSArray *nodesOfKindInSection = nodesOfKind[section]; + NSInteger itemIndex = indexPath.item; + if (itemIndex < nodesOfKindInSection.count) { + return nodesOfKindInSection[itemIndex]; + } + } + ASDisplayNodeAssert(NO, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self.collectionDataSource); + return [[ASCellNode alloc] init]; } #pragma mark - Private Helpers diff --git a/examples/CustomCollectionView/Sample/ViewController.m b/examples/CustomCollectionView/Sample/ViewController.m index b28fd7b652..3513db33df 100644 --- a/examples/CustomCollectionView/Sample/ViewController.m +++ b/examples/CustomCollectionView/Sample/ViewController.m @@ -101,11 +101,13 @@ static NSUInteger kNumberOfImages = 14; return [[SupplementaryNode alloc] initWithText:text]; } -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView +{ return _sections.count; } -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ return [_sections[section] count]; } From 93498d8240196d5ca764c23d0bafb9b38dd998ba Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 27 Dec 2015 15:35:43 -0800 Subject: [PATCH 06/39] [ASDisplayNode] Preserve contents after non-range-managed nodes are removed from superviews or windows. This behavior changed in 1.9.3 and introduced flickering in some cases. Preserving the contents is closer to UIKit behavior. --- AsyncDisplayKit/ASDisplayNode.mm | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 50bd7e5e12..facba9f6b9 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1775,18 +1775,22 @@ static BOOL ShouldUseNewRenderingRange = NO; if (newState & ASInterfaceStateFetchData) { [self fetchData]; } else { - [self clearFetchedData]; + if ([self supportsRangeManagedInterfaceState]) { + [self clearFetchedData]; + } } } // Entered or exited contents rendering state. if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { - if (newState & ASInterfaceStateDisplay) { - // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. - [self setDisplaySuspended:NO]; - } else { - [self setDisplaySuspended:YES]; - [self clearContents]; + if ([self supportsRangeManagedInterfaceState]) { + if (newState & ASInterfaceStateDisplay) { + // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. + [self setDisplaySuspended:NO]; + } else { + [self setDisplaySuspended:YES]; + [self clearContents]; + } } } From 55861b3de1e5e1a64cecdabdfd8b68e8defd40e7 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 27 Dec 2015 16:37:33 -0800 Subject: [PATCH 07/39] Update the expectations of the tests now that the clearContents behavior is corrected. --- AsyncDisplayKit/ASDisplayNode.mm | 4 ++++ AsyncDisplayKitTests/ASDisplayNodeTests.m | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index facba9f6b9..42c4bf79a9 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1770,6 +1770,10 @@ static BOOL ShouldUseNewRenderingRange = NO; // Trigger asynchronous measurement if it is not already cached or being calculated. } + // For the FetchData and Display ranges, we don't want to call -clear* if not being managed by a range controller. + // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. + // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. + // Entered or exited data loading state. if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { if (newState & ASInterfaceStateFetchData) { diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 4c45aeadba..e343e115b3 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -1702,7 +1702,11 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); [node.view removeFromSuperview]; - XCTAssert(!node.hasFetchedData); + // We don't want to call -clearFetchedData on nodes that aren't being managed by a range controller. + // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. + // Still, the interfaceState should be None to reflect the current state of the node. + // We just don't proactively clear contents or fetched data for this state transition. + XCTAssert(node.hasFetchedData); XCTAssert(node.interfaceState == ASInterfaceStateNone); } From b75b72c6600f56349d73f06702c709757b1690c5 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 27 Dec 2015 16:38:34 -0800 Subject: [PATCH 08/39] Improve handling of file URLs for ASNetworkImageNode. Details discussed in https://github.com/facebook/AsyncDisplayKit/pull/1003. This PR supercedes that one. --- AsyncDisplayKit/ASNetworkImageNode.mm | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) mode change 100644 => 100755 AsyncDisplayKit/ASNetworkImageNode.mm diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm old mode 100644 new mode 100755 index c1437ef814..cbba35af9c --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -188,14 +188,23 @@ ASDN::MutexLocker l(_lock); dispatch_async(dispatch_get_main_queue(), ^{ - _imageLoaded = YES; - if (self.shouldCacheImage) { - self.image = [UIImage imageNamed:_URL.path]; + self.image = [UIImage imageNamed:_URL.path.lastPathComponent]; } else { + // First try to load the path directly, for efficiency assuming a developer who + // doesn't want caching is trying to be as minimal as possible. self.image = [UIImage imageWithContentsOfFile:_URL.path]; + if (!self.image) { + // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the + // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. + NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; + if (filename) { + self.image = [UIImage imageWithContentsOfFile:filename]; + } + } } - + + _imageLoaded = YES; [_delegate imageNode:self didLoadImage:self.image]; }); } From 382326ea84d6bf2afe57a5a8b28b141b5c155ef0 Mon Sep 17 00:00:00 2001 From: yury Date: Mon, 28 Dec 2015 16:11:50 +0300 Subject: [PATCH 09/39] Bridge UITableViewCell pointInside to ASNodeCell Allow ASNodeCell to specify pointInside of UITableViewCell. This is very usefull, if ASNodeCell is presented as bubble aligned to left or right (like Messages.app) and we need to be able to select row only if user taps on bubble. --- AsyncDisplayKit/ASTableView.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 36c990b993..76db980ee9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -75,6 +75,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _node.highlighted = highlighted; } +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { + return [_node pointInside:point withEvent:event]; +} + @end #pragma mark - From 2ff683bc0b85d18754f1384b310b41478ceb1853 Mon Sep 17 00:00:00 2001 From: Natasha Murashev Date: Tue, 29 Dec 2015 16:54:20 +1300 Subject: [PATCH 10/39] Added Swift example to README --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index f7743eced1..d9f53c4b87 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,22 @@ dispatch_async(_backgroundQueue, ^{ }); ``` +In Swift: + +```swift +dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) { + let node = ASTextNode() + node.attributedString = NSAttributedString(string: "hello") + node.measure(CGSize(width: screenWidth, height: CGFloat.max)) + node.frame = CGRect(origin: CGPointZero, size: node.calculatedSize) + + // self.view isn't a node, so we can only use it on the main thread + dispatch_async(dispatch_get_main_queue()) { + self.view.addSubview(node.view) + } + } +``` + AsyncDisplayKit at a glance: * `ASImageNode` and `ASTextNode` are drop-in replacements for UIImageView and From 90af0d393b69f93656f79cd807e8665b75ed36a4 Mon Sep 17 00:00:00 2001 From: Natasha Murashev Date: Tue, 29 Dec 2015 16:55:48 +1300 Subject: [PATCH 11/39] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d9f53c4b87..4594f40990 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,16 @@ In Swift: ```swift dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) { - let node = ASTextNode() - node.attributedString = NSAttributedString(string: "hello") - node.measure(CGSize(width: screenWidth, height: CGFloat.max)) - node.frame = CGRect(origin: CGPointZero, size: node.calculatedSize) + let node = ASTextNode() + node.attributedString = NSAttributedString(string: "hello") + node.measure(CGSize(width: screenWidth, height: CGFloat.max)) + node.frame = CGRect(origin: CGPointZero, size: node.calculatedSize) - // self.view isn't a node, so we can only use it on the main thread - dispatch_async(dispatch_get_main_queue()) { - self.view.addSubview(node.view) - } - } + // self.view isn't a node, so we can only use it on the main thread + dispatch_async(dispatch_get_main_queue()) { + self.view.addSubview(node.view) + } +} ``` AsyncDisplayKit at a glance: From 83960065ed6a6539929408c852338fb3861f186f Mon Sep 17 00:00:00 2001 From: yury Date: Tue, 29 Dec 2015 12:10:08 +0300 Subject: [PATCH 12/39] Code style fix --- AsyncDisplayKit/ASTableView.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 76db980ee9..1b095adb59 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -75,8 +75,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _node.highlighted = highlighted; } -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { - return [_node pointInside:point withEvent:event]; +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + return [_node pointInside:point withEvent:event]; } @end From 1870208153fa64e66d1deb3d08d5d94b0779b952 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 29 Dec 2015 20:46:42 -0800 Subject: [PATCH 13/39] [ASPagerNode] Ensure delegate property can be set before the view is loaded and is not overwritten. --- AsyncDisplayKit/ASPagerNode.h | 5 ++- AsyncDisplayKit/ASPagerNode.m | 15 ++++--- .../Sample/ViewController.m | 42 +++++++------------ 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 746a805514..6396d4b332 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -26,13 +26,14 @@ - (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; // The underlying ASCollectionView object. -- (ASCollectionView *)collectionView; +@property (nonatomic, readonly) ASCollectionView *view; // Delegate is optional, and uses the same protocol as ASCollectionNode. // This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... -@property (weak, nonatomic) id delegate; +@property (nonatomic, weak) id delegate; // Data Source is required, and uses a different protocol from ASCollectionNode. +//@property (nonatomic, weak) id dataSource; - (void)setDataSource:(id )dataSource; - (id )dataSource; diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index b8a52f735e..8a7c7fce63 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -19,7 +19,7 @@ @end @implementation ASPagerNode -@dynamic delegate; +@dynamic view, delegate, dataSource; - (instancetype)init { @@ -31,6 +31,12 @@ return [self initWithFlowLayout:flowLayout]; } +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout +{ + ASDisplayNodeAssert([layout isKindOfClass:[UICollectionViewFlowLayout class]], @"ASPagerNode requires a flow layout."); + return [self initWithFlowLayout:(UICollectionViewFlowLayout *)layout]; +} + - (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout { self = [super initWithCollectionViewLayout:flowLayout]; @@ -40,11 +46,6 @@ return self; } -- (ASCollectionView *)collectionView -{ - return self.view; -} - - (void)setDataSource:(id )pagerDataSource { if (pagerDataSource != _pagerDataSource) { @@ -69,8 +70,6 @@ [super didLoad]; ASCollectionView *cv = self.view; - cv.asyncDataSource = self; - cv.asyncDelegate = self; cv.pagingEnabled = YES; cv.allowsSelection = NO; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index 8e6131654e..b0e4497a08 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -10,14 +10,12 @@ */ #import -#import - #import "ViewController.h" #import "GradientTableNode.h" -@interface ViewController () +@interface ViewController () { - ASCollectionView *_pagerView; + ASPagerNode *_pagerNode; } @end @@ -31,23 +29,12 @@ { if (!(self = [super init])) return nil; - - UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; - flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; -// flowLayout.itemSize = [[UIScreen mainScreen] bounds].size; - flowLayout.minimumInteritemSpacing = 0; - flowLayout.minimumLineSpacing = 0; - _pagerView = [[ASCollectionView alloc] initWithCollectionViewLayout:flowLayout]; + _pagerNode = [[ASPagerNode alloc] init]; + _pagerNode.dataSource = self; - ASRangeTuningParameters rangeTuningParameters; - rangeTuningParameters.leadingBufferScreenfuls = 1.0; - rangeTuningParameters.trailingBufferScreenfuls = 1.0; - [_pagerView setTuningParameters:rangeTuningParameters forRangeType:ASLayoutRangeTypeRender]; - - _pagerView.pagingEnabled = YES; - _pagerView.asyncDataSource = self; - _pagerView.asyncDelegate = self; + // Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView. + //_pagerNode.delegate = self; self.title = @"Paging Table Nodes"; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo @@ -59,20 +46,19 @@ - (void)reloadEverything { - [_pagerView reloadData]; + [_pagerNode reloadData]; } - (void)viewDidLoad { [super viewDidLoad]; - [self.view addSubview:_pagerView]; + [self.view addSubnode:_pagerNode]; } - (void)viewWillLayoutSubviews { - _pagerView.frame = self.view.bounds; - _pagerView.contentInset = UIEdgeInsetsZero; + _pagerNode.frame = self.view.bounds; } - (BOOL)prefersStatusBarHidden @@ -81,20 +67,20 @@ } #pragma mark - -#pragma mark ASTableView. +#pragma mark ASPagerNode. -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; { - CGSize boundsSize = collectionView.bounds.size; + CGSize boundsSize = pagerNode.bounds.size; CGSize gradientRowSize = CGSizeMake(boundsSize.width, 100); GradientTableNode *node = [[GradientTableNode alloc] initWithElementSize:gradientRowSize]; node.preferredFrameSize = boundsSize; return node; } -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode { - return (section == 0 ? 10 : 0); + return 10; } @end From 051f1f6cdda80e0684dc90583b72ef15917732ac Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 29 Dec 2015 22:24:44 -0800 Subject: [PATCH 14/39] Use _ASDisplayLayer for both ASTableView and ASCollectionView. zeroContentInsets to fix UIKit. --- AsyncDisplayKit/ASCollectionView.h | 10 ++++++ AsyncDisplayKit/ASCollectionView.mm | 12 ++++++- AsyncDisplayKit/ASPagerNode.h | 2 ++ AsyncDisplayKit/ASPagerNode.m | 55 +++++++++++++++++------------ AsyncDisplayKit/ASTableView.mm | 8 ++++- 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index fb818eb7e6..ec88645ea7 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -288,6 +288,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)clearFetchedData; +/** + * Forces the .contentInset to be UIEdgeInsetsZero. + * + * @discussion By default, UIKit sets the top inset to the navigation bar height, even for horizontally + * scrolling views. This can only be disabled by setting a property on the containing UIViewController, + * automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure + * its flow layout behaves predictably and does not log undefined layout warnings. + */ +@property (nonatomic) BOOL zeroContentInsets; + @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 2e7c17c017..754fd761fb 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -17,6 +17,7 @@ #import "ASInternalHelpers.h" #import "ASRangeController.h" #import "UICollectionViewLayout+ASConvenience.h" +#import "_ASDisplayLayer.h" static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; @@ -113,6 +114,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; @implementation ASCollectionView +// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASCollectionNode. ++ (Class)layerClass +{ + return [_ASDisplayLayer class]; +} + #pragma mark - #pragma mark Lifecycle. @@ -221,7 +228,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set."); ASPerformBlockOnMainThread(^{ _superIsPendingDataLoad = YES; [super reloadData]; @@ -557,6 +563,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)layoutSubviews { + if (_zeroContentInsets) { + self.contentInset = UIEdgeInsetsZero; + } + if (! CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, self.bounds.size)) { _maxSizeForNodesConstrainedSize = self.bounds.size; diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 6396d4b332..e715e48bda 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -17,6 +17,8 @@ - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; @end +// WARNING: ASPagerNode is new in AsyncDisplayKit 1.9.4 and not yet widely tested. +// Details of its API or behavior may change in future releases @interface ASPagerNode : ASCollectionNode // Configures a default horizontal, paging flow layout with 0 inter-item spacing. diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 8a7c7fce63..4bb338439c 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -10,7 +10,8 @@ #import "ASDelegateProxy.h" #import "ASDisplayNode+Subclasses.h" -@interface ASPagerNode () { +@interface ASPagerNode () +{ UICollectionViewFlowLayout *_flowLayout; ASPagerNodeProxy *_proxy; id _pagerDataSource; @@ -46,25 +47,6 @@ return self; } -- (void)setDataSource:(id )pagerDataSource -{ - if (pagerDataSource != _pagerDataSource) { - _pagerDataSource = pagerDataSource; - _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; - super.dataSource = (id )_proxy; - } -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - [self setDataSource:nil]; -} - -- (id )dataSource -{ - return _pagerDataSource; -} - - (void)didLoad { [super didLoad]; @@ -77,6 +59,11 @@ cv.showsHorizontalScrollIndicator = NO; cv.scrollsToTop = NO; + // Zeroing contentInset is important, as UIKit will set the top inset for the navigation bar even though + // our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning. + // From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets). + cv.zeroContentInsets = YES; + ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; [self setTuningParameters:preloadParams forRangeType:ASLayoutRangeTypePreload]; @@ -95,13 +82,14 @@ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); - return [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); + ASCellNode *pageNode = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; + return pageNode; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); return [_pagerDataSource numberOfPagesInPagerNode:self]; } @@ -110,4 +98,25 @@ return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); } +#pragma mark - Data Source Proxy + +- (id )dataSource +{ + return _pagerDataSource; +} + +- (void)setDataSource:(id )pagerDataSource +{ + if (pagerDataSource != _pagerDataSource) { + _pagerDataSource = pagerDataSource; + _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + super.dataSource = (id )_proxy; + } +} + +- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy +{ + [self setDataSource:nil]; +} + @end diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 36c990b993..71e0c93cbf 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -18,6 +18,7 @@ #import "ASLayout.h" #import "ASLayoutController.h" #import "ASRangeController.h" +#import "_ASDisplayLayer.h" #import @@ -120,6 +121,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; @implementation ASTableView +// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASTableNode. ++ (Class)layerClass +{ + return [_ASDisplayLayer class]; +} + + (Class)dataControllerClass { return [ASChangeSetDataController class]; @@ -280,7 +287,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set."); ASPerformBlockOnMainThread(^{ [super reloadData]; }); From df3ce787f7014e2f4f8df8acf3d3eae5a41262db Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 29 Dec 2015 23:11:33 -0800 Subject: [PATCH 15/39] Ensure that the uncommon __unloadNode codepath does not unintentionally trigger node removal. --- AsyncDisplayKit/ASDisplayNode.mm | 3 ++- AsyncDisplayKit/Details/_ASDisplayView.mm | 7 +++++-- AsyncDisplayKit/Private/_ASPendingState.m | 10 ++++++++++ examples/SocialAppLayout/Sample/ViewController.m | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 42c4bf79a9..ba941a9065 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -352,8 +352,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)__unloadNode { ASDisplayNodeAssertThreadAffinity(self); + ASDisplayNodeAssert([self isNodeLoaded], @"Implementation shouldn't call __unloadNode if not loaded: %@", self); ASDN::MutexLocker l(_propertyLock); - + if (_flags.layerBacked) _pendingViewState = [_ASPendingState pendingViewStateFromLayer:_layer]; else diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 0d4e8c77ca..f542b29db6 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -151,8 +151,11 @@ needsSupernodeRemoval = YES; } } else { - // If supernode is loaded but our superview is nil, the user manually removed us, so disconnect supernode. - needsSupernodeRemoval = supernodeLoaded; + // If supernode is loaded but our superview is nil, the user likely manually removed us, so disconnect supernode. + // The unlikely alternative: we are in __unloadNode, with shouldRasterizeSubnodes just having been turned on. + // In the latter case, we don't want to disassemble the node hierarchy because all views are intentionally being destroyed. + BOOL nodeIsRasterized = ((_node.hierarchyState & ASHierarchyStateRasterized) == ASHierarchyStateRasterized); + needsSupernodeRemoval = (supernodeLoaded && !nodeIsRasterized); } if (needsSupernodeRemoval) { diff --git a/AsyncDisplayKit/Private/_ASPendingState.m b/AsyncDisplayKit/Private/_ASPendingState.m index 8d2c8f7665..37918e197b 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.m +++ b/AsyncDisplayKit/Private/_ASPendingState.m @@ -798,8 +798,13 @@ static UIColor *defaultTintColor = nil; view.accessibilityIdentifier = accessibilityIdentifier; } +// FIXME: Make this more efficient by tracking which properties are set rather than reading everything. + (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer { + if (!layer) { + return nil; + } + _ASPendingState *pendingState = [[_ASPendingState alloc] init]; pendingState.anchorPoint = layer.anchorPoint; @@ -877,8 +882,13 @@ static UIColor *defaultTintColor = nil; return pendingState; } +// FIXME: Make this more efficient by tracking which properties are set rather than reading everything. + (_ASPendingState *)pendingViewStateFromView:(UIView *)view { + if (!view) { + return nil; + } + _ASPendingState *pendingState = [[_ASPendingState alloc] init]; CALayer *layer = view.layer; diff --git a/examples/SocialAppLayout/Sample/ViewController.m b/examples/SocialAppLayout/Sample/ViewController.m index 1c089db9a3..ff6abbd3f8 100644 --- a/examples/SocialAppLayout/Sample/ViewController.m +++ b/examples/SocialAppLayout/Sample/ViewController.m @@ -134,4 +134,18 @@ return _socialAppDataSource.count; } +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + PostNode *postNode = (PostNode *)[_tableView nodeForRowAtIndexPath:indexPath]; + Post *post = _socialAppDataSource[indexPath.row]; + + BOOL shouldRasterize = postNode.shouldRasterizeDescendants; + shouldRasterize = !shouldRasterize; + postNode.shouldRasterizeDescendants = shouldRasterize; + + NSLog(@"%@ rasterization for %@'s post: %@", shouldRasterize ? @"Enabling" : @"Disabling", post.name, postNode); + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + @end From ffcddf36e210c07b7c7cda164f96c9b9eaf9607b Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 30 Dec 2015 22:42:11 -0800 Subject: [PATCH 16/39] Ensure that ASRangeController immediately removes any deleted nodes from its range state. --- AsyncDisplayKit/ASCollectionView.mm | 19 ++++++ AsyncDisplayKit/ASPagerNode.h | 15 ++--- AsyncDisplayKit/ASPagerNode.m | 11 +--- AsyncDisplayKit/Details/ASDelegateProxy.h | 4 +- AsyncDisplayKit/Details/ASDelegateProxy.m | 8 ++- AsyncDisplayKit/Details/ASLayoutRangeType.h | 2 +- AsyncDisplayKit/Details/ASRangeController.mm | 64 ++++++++++++------- .../Sample/GradientTableNode.h | 2 + .../Sample/GradientTableNode.mm | 7 ++ .../Sample/RandomCoreGraphicsNode.h | 2 + .../Sample/RandomCoreGraphicsNode.m | 20 ++++++ .../Sample/ViewController.m | 3 +- 12 files changed, 110 insertions(+), 47 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 754fd761fb..7706caa066 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -927,4 +927,23 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +#pragma mark - UICollectionView dead-end intercepts + +#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting. + +// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) + +- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0) +{ + ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); + return NO; +} + +- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0) +{ + ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); +} + +#endif + @end diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index e715e48bda..fa992eb1b5 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -17,27 +17,24 @@ - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; @end -// WARNING: ASPagerNode is new in AsyncDisplayKit 1.9.4 and not yet widely tested. -// Details of its API or behavior may change in future releases @interface ASPagerNode : ASCollectionNode // Configures a default horizontal, paging flow layout with 0 inter-item spacing. - (instancetype)init; // Initializer with custom-configured flow layout properties. -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout; -// The underlying ASCollectionView object. -@property (nonatomic, readonly) ASCollectionView *view; +// Data Source is required, and uses a different protocol from ASCollectionNode. +- (void)setDataSource:(id )dataSource; +- (id )dataSource; // Delegate is optional, and uses the same protocol as ASCollectionNode. // This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... @property (nonatomic, weak) id delegate; -// Data Source is required, and uses a different protocol from ASCollectionNode. -//@property (nonatomic, weak) id dataSource; -- (void)setDataSource:(id )dataSource; -- (id )dataSource; +// The underlying ASCollectionView object. +@property (nonatomic, readonly) ASCollectionView *view; - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 4bb338439c..95d25f268c 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -29,17 +29,12 @@ flowLayout.minimumInteritemSpacing = 0; flowLayout.minimumLineSpacing = 0; - return [self initWithFlowLayout:flowLayout]; + return [self initWithCollectionViewLayout:flowLayout]; } -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout -{ - ASDisplayNodeAssert([layout isKindOfClass:[UICollectionViewFlowLayout class]], @"ASPagerNode requires a flow layout."); - return [self initWithFlowLayout:(UICollectionViewFlowLayout *)layout]; -} - -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout +- (instancetype)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)flowLayout; { + ASDisplayNodeAssert([flowLayout isKindOfClass:[UICollectionViewFlowLayout class]], @"ASPagerNode requires a flow layout."); self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { _flowLayout = flowLayout; diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.h b/AsyncDisplayKit/Details/ASDelegateProxy.h index 850328a8ec..8c4f6eaa5e 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.h +++ b/AsyncDisplayKit/Details/ASDelegateProxy.h @@ -9,7 +9,7 @@ #import @class ASDelegateProxy; -@protocol ASDelegateProxyInterceptor +@protocol ASDelegateProxyInterceptor @required // Called if the target object is discovered to be nil if it had been non-nil at init time. // This happens if the object is deallocated, because the proxy must maintain a weak reference to avoid cycles. @@ -25,7 +25,7 @@ @interface ASDelegateProxy : NSProxy -- (instancetype)initWithTarget:(id)target interceptor:(id )interceptor; +- (instancetype)initWithTarget:(id )target interceptor:(id )interceptor; // This method must be overridden by a subclass. - (BOOL)interceptsSelector:(SEL)selector; diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 8054b2b018..783b2e8bc8 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -54,7 +54,11 @@ selector == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) || // used for batch fetching API - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) || + + // intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) + selector == @selector(collectionView:canMoveItemAtIndexPath:) || + selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) ); } @@ -97,7 +101,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector { if ([self interceptsSelector:aSelector]) { - return (_interceptor != nil); + return [_interceptor respondsToSelector:aSelector]; } else { // Also return NO if _target has become nil due to zeroing weak reference (or placeholder initialization). return [_target respondsToSelector:aSelector]; diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index a5f2075485..a1e51b5ac6 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -9,7 +9,7 @@ #import typedef NS_ENUM(NSInteger, ASLayoutRangeType) { - ASLayoutRangeTypeVisible, + ASLayoutRangeTypeVisible = 0, ASLayoutRangeTypeRender, ASLayoutRangeTypePreload, ASLayoutRangeTypeCount diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index ccbeff0c66..519c327f0f 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -130,7 +130,8 @@ rangeType:rangeType]; // Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths - NSMutableSet *removedIndexPaths = _rangeIsValid ? [_rangeTypeIndexPaths[rangeKey] mutableCopy] : [NSMutableSet set]; + // This value may be nil for the first call of this method. + NSMutableSet *removedIndexPaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy]; [removedIndexPaths minusSet:indexPaths]; [removedIndexPaths minusSet:visibleNodePathsSet]; @@ -176,61 +177,76 @@ #pragma mark - ASDataControllerDelegete -- (void)dataControllerBeginUpdates:(ASDataController *)dataController { +- (void)dataControllerBeginUpdates:(ASDataController *)dataController +{ ASPerformBlockOnMainThread(^{ [_delegate didBeginUpdatesInRangeController:self]; }); } -- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { +- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +{ ASPerformBlockOnMainThread(^{ [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; }); } -- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { +- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - - NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count]; - [nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) { - [nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]]; - }]; - ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { +- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; + + // When removing nodes we need to make sure that removed indexPaths are not left in _rangeTypeIndexPaths, + // otherwise _updateVisibleNodeIndexPaths may try to retrieve nodes from dataSource that aren't there anymore + for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { + id rangeKey = @((ASLayoutRangeType)i); + NSMutableSet *rangePaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy]; + for (NSIndexPath *path in indexPaths) { + [rangePaths removeObject:path]; + } + _rangeTypeIndexPaths[rangeKey] = rangePaths; + } + [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); - - NSMutableArray *sectionNodeSizes = [NSMutableArray arrayWithCapacity:sections.count]; - - [sections enumerateObjectsUsingBlock:^(NSArray *nodes, NSUInteger idx, BOOL *stop) { - NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count]; - [nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx2, BOOL *stop2) { - [nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]]; - }]; - [sectionNodeSizes addObject:nodeSizes]; - }]; - ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } -- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { +- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; + + // When removing nodes we need to make sure that removed indexPaths are not left in _rangeTypeIndexPaths, + // otherwise _updateVisibleNodeIndexPaths may try to retrieve nodes from dataSource that aren't there anymore + for (NSInteger i = 0; i < ASLayoutRangeTypeCount; i++) { + id rangeKey = @((ASLayoutRangeType)i); + NSMutableSet *rangePaths = [_rangeTypeIndexPaths[rangeKey] mutableCopy]; + for (NSIndexPath *path in _rangeTypeIndexPaths[rangeKey]) { + if ([indexSet containsIndex:path.section]) { + [rangePaths removeObject:path]; + } + } + _rangeTypeIndexPaths[rangeKey] = rangePaths; + } + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.h b/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.h index c6854c38e3..f50b74b983 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.h +++ b/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.h @@ -19,4 +19,6 @@ - (instancetype)initWithElementSize:(CGSize)size; +@property (nonatomic) NSInteger pageNumber; + @end diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm b/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm index 79c3ab69d6..6d2c5c3d6b 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm +++ b/examples/VerticalWithinHorizontalScrolling/Sample/GradientTableNode.mm @@ -63,9 +63,16 @@ { RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; elementNode.preferredFrameSize = _elementSize; + elementNode.indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:_pageNumber]; return elementNode; } +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + [_tableNode.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; +} + - (void)layout { [super layout]; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h index f4324b5d3e..4e111ad003 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h +++ b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.h @@ -10,4 +10,6 @@ @interface RandomCoreGraphicsNode : ASCellNode +@property (nonatomic) NSIndexPath *indexPath; + @end diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m index 7821f9af5f..b9fb37be37 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m @@ -42,4 +42,24 @@ CGColorSpaceRelease(colorSpace); } +#if 0 +- (void)fetchData +{ + NSLog(@"fetchData - %@, %@", self, self.indexPath); + [super fetchData]; +} + +- (void)clearFetchedData +{ + NSLog(@"clearFetchedData - %@, %@", self, self.indexPath); + [super clearFetchedData]; +} + +- (void)visibilityDidChange:(BOOL)isVisible +{ + NSLog(@"visibilityDidChange:%d - %@, %@", isVisible, self, self.indexPath); + [super visibilityDidChange:isVisible]; +} +#endif + @end diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index b0e4497a08..0e7fa9a317 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -69,12 +69,13 @@ #pragma mark - #pragma mark ASPagerNode. -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index { CGSize boundsSize = pagerNode.bounds.size; CGSize gradientRowSize = CGSizeMake(boundsSize.width, 100); GradientTableNode *node = [[GradientTableNode alloc] initWithElementSize:gradientRowSize]; node.preferredFrameSize = boundsSize; + node.pageNumber = index; return node; } From 50c97b263617a4f8694485f4169e9f11fb2304f1 Mon Sep 17 00:00:00 2001 From: appleguy Date: Wed, 30 Dec 2015 23:42:18 -0800 Subject: [PATCH 17/39] Revert "[ASCellNode] Forward pointInside to node implementation from UITableViewCell." --- AsyncDisplayKit/ASTableView.mm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 5748645db3..71e0c93cbf 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -76,11 +76,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _node.highlighted = highlighted; } -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - return [_node pointInside:point withEvent:event]; -} - @end #pragma mark - From df9f4333535b894216ff1bfd2dd36d02d9d85e36 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 31 Dec 2015 00:21:20 -0800 Subject: [PATCH 18/39] Prevent calling unsupported UICollectionViewDelegate methods (supplementary view appearance) --- AsyncDisplayKit/ASCellNode.m | 7 ------- AsyncDisplayKit/ASCollectionView.mm | 10 ++++++++++ AsyncDisplayKit/Details/ASDelegateProxy.m | 4 +++- examples/CustomCollectionView/Sample/ViewController.m | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 16d8f32d83..575200df1e 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -63,13 +63,6 @@ return self; } -//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -//{ -// _viewControllerNode.frame = (CGRect){{0,0}, constrainedSize.max}; -// NSLog(@"%f %f", constrainedSize.max.width, constrainedSize.max.height); -// return [super layoutSpecThatFits:constrainedSize]; -//} - - (void)layout { [super layout]; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 7706caa066..cc6626f346 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -944,6 +944,16 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); } +- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this delegate method.", NSStringFromSelector(_cmd)); +} + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this delegate method.", NSStringFromSelector(_cmd)); +} + #endif @end diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 783b2e8bc8..6f3594ecc8 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -58,7 +58,9 @@ // intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) selector == @selector(collectionView:canMoveItemAtIndexPath:) || - selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) + selector == @selector(collectionView:moveItemAtIndexPath:toIndexPath:) || + selector == @selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:) || + selector == @selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:) ); } diff --git a/examples/CustomCollectionView/Sample/ViewController.m b/examples/CustomCollectionView/Sample/ViewController.m index 3513db33df..f8b6071bad 100644 --- a/examples/CustomCollectionView/Sample/ViewController.m +++ b/examples/CustomCollectionView/Sample/ViewController.m @@ -54,7 +54,7 @@ static NSUInteger kNumberOfImages = 14; _layoutInspector = [[MosaicCollectionViewLayoutInspector alloc] init]; - _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES]; + _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.asyncDataSource = self; _collectionView.asyncDelegate = self; _collectionView.layoutInspector = _layoutInspector; From aee7b3b77a90c2153e3ac9aadb209a3990ddc8b2 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 31 Dec 2015 01:52:08 -0800 Subject: [PATCH 19/39] Remove padding workaround from old CoreText days. This is a vestige from Paper. Discussion: https://github.com/facebook/AsyncDisplayKit/issues/1013 --- AsyncDisplayKit/ASTextNode.mm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index fdcfc26b19..9ad5437b59 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -413,9 +413,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (backgroundColor) { CGContextSetFillColorWithColor(context, backgroundColor); CGContextSetBlendMode(context, kCGBlendModeCopy); - // outset the background fill to cover fractional errors when drawing at a - // small contentsScale. - CGContextFillRect(context, CGRectInset(bounds, -2, -2)); + CGContextFillRect(context, CGContextGetClipBoundingBox(context)); CGContextSetBlendMode(context, kCGBlendModeNormal); } } From 8f914f8430cb4d1a473d888f1db41708ccd0d5ae Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 31 Dec 2015 15:38:32 -0800 Subject: [PATCH 20/39] Fix logic error in handling enablement of beta display range. --- AsyncDisplayKit/Details/ASRangeHandlerRender.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index 6dcf11044d..af05d29cc0 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -64,7 +64,7 @@ [node enterInterfaceState:ASInterfaceStateDisplay]; - if (![ASDisplayNode shouldUseNewRenderingRange]) { + if ([ASDisplayNode shouldUseNewRenderingRange]) { [node recursivelyEnsureDisplaySynchronously:NO]; } else { // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. @@ -101,7 +101,7 @@ // The node calls clearCurrentContents and suspends display [node exitInterfaceState:ASInterfaceStateDisplay]; - if (![ASDisplayNode shouldUseNewRenderingRange]) { + if ([ASDisplayNode shouldUseNewRenderingRange]) { if (![node isLayerBacked]) { [node.view removeFromSuperview]; } else { From 7a9cd1f930b9d67a4a250c062bec1ea8a6914e5b Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 31 Dec 2015 17:59:59 -0800 Subject: [PATCH 21/39] [ASRangeController] Introduce totally rewritten range controller based on ASInterfaceState. --- .../Details/ASRangeControllerBeta.h | 20 ++ .../Details/ASRangeControllerBeta.mm | 210 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 AsyncDisplayKit/Details/ASRangeControllerBeta.h create mode 100644 AsyncDisplayKit/Details/ASRangeControllerBeta.mm diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.h b/AsyncDisplayKit/Details/ASRangeControllerBeta.h new file mode 100644 index 0000000000..7c8385f8c1 --- /dev/null +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// ASRangeControllerBeta defined in ASRangeController.h + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm new file mode 100644 index 0000000000..40861a0fe4 --- /dev/null +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -0,0 +1,210 @@ +/* Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "ASRangeControllerBeta.h" + +#import "ASAssert.h" +#import "ASDisplayNodeExtras.h" +#import "ASMultiDimensionalArrayUtils.h" +#import "ASRangeHandlerVisible.h" +#import "ASRangeHandlerRender.h" +#import "ASRangeHandlerPreload.h" +#import "ASInternalHelpers.h" +#import "ASDisplayNode+FrameworkPrivate.h" + +@interface ASRangeControllerBeta () +{ + BOOL _rangeIsValid; + BOOL _queuedRangeUpdate; + ASScrollDirection _scrollDirection; +} + +@end + +@implementation ASRangeControllerBeta + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _rangeIsValid = YES; + + return self; +} + +#pragma mark - Core visible node range managment API + +- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection +{ + _scrollDirection = scrollDirection; + + if (_queuedRangeUpdate) { + return; + } + + // coalesce these events -- handling them multiple times per runloop is noisy and expensive + _queuedRangeUpdate = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self _updateVisibleNodeIndexPaths]; + }); +} + +- (void)_updateVisibleNodeIndexPaths +{ + if (!_queuedRangeUpdate) { + return; + } + + // FIXME: Consider if we need to check this separately from the range calculation below. + NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; + + if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + _queuedRangeUpdate = NO; + return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later + } + + CGSize viewportSize = [_dataSource viewportSizeForRangeController:self]; + + // 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 *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]; + [allIndexPaths unionSet:displayIndexPaths]; + [allIndexPaths unionSet:visibleIndexPaths]; + + NSMutableArray *modified = [NSMutableArray array]; + + for (NSIndexPath *indexPath in allIndexPaths) { + // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. + // For consistency, make sure each node knows that it should measure itself if something changes. + ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; + + if ([fetchDataIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateFetchData; + } + if ([displayIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateDisplay; + } + if ([visibleIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateVisible; + } + + ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); + // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. + if (node.interfaceState != interfaceState) { + [modified addObject:indexPath]; + [node recursivelySetInterfaceState:interfaceState]; + } + } + +/* + [modified sortUsingSelector:@selector(compare:)]; + + for (NSIndexPath *indexPath in modified) { + NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, [visibleIndexPaths containsObject:indexPath], [displayIndexPaths containsObject:indexPath], [fetchDataIndexPaths containsObject:indexPath]); + } +*/ + + _rangeIsValid = YES; + _queuedRangeUpdate = NO; +} + +#pragma mark - Cell node view handling + +- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); + ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); + + if (node.view.superview == contentView) { + // this content view is already correctly configured + return; + } + + // clean the content view + for (UIView *view in contentView.subviews) { + [view removeFromSuperview]; + } + + [contentView addSubview:node.view]; +} + +#pragma mark - ASDataControllerDelegete + +- (void)dataControllerBeginUpdates:(ASDataController *)dataController +{ + ASPerformBlockOnMainThread(^{ + [_delegate didBeginUpdatesInRangeController:self]; + }); +} + +- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion +{ + ASPerformBlockOnMainThread(^{ + [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + +- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + }); +} + +@end From b273f358f586d71b9e66a18b30e3c505b3946763 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 1 Jan 2016 00:48:44 -0800 Subject: [PATCH 22/39] [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 { From 1d1a248167fcb5151ff4f2ec8145be9581df1e5f Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 1 Jan 2016 01:35:11 -0800 Subject: [PATCH 23/39] Bump podspec to 1.9.5 for upcoming development cycle, now that 1.9.4 is tagged and launched. --- AsyncDisplayKit.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index a800f93bbf..5755b452fa 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.9.4' + spec.version = '1.9.5' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.4' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.5' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' From 73373ee17ba4717d891647add42294ac78f15d98 Mon Sep 17 00:00:00 2001 From: Rajinder Ramgarhia Date: Fri, 1 Jan 2016 22:27:08 -0500 Subject: [PATCH 24/39] Fix ASControlNode mutation crash --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ++++++++++++++++ AsyncDisplayKit/ASControlNode.m | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index c622894086..d970da04e2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1547,6 +1547,7 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, + 2AC85249A2221047409FABBB /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1646,6 +1647,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2AC85249A2221047409FABBB /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 2E61B6A0DB0F436A9DDBE86F /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 8278b9a2fc..160e2867d8 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -339,7 +339,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ (ASControlNodeEvent controlEvent) { - NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; + NSMapTable *eventDispatchTable = [[_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)] copy]; // For each target interested in this event... for (id target in eventDispatchTable) From 25b6f29584ef720668886ddc82af724fcf466003 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 1 Jan 2016 23:36:00 -0800 Subject: [PATCH 25/39] Introduce ASCollectionInternal file and create always-available ASCView -> ASCNode backpointer. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 24 ++++++--- ...ASCollectionNode.m => ASCollectionNode.mm} | 49 ++++++++++++++++--- AsyncDisplayKit/ASCollectionView.h | 4 ++ AsyncDisplayKit/ASCollectionView.mm | 18 +++++-- .../Details/ASAbstractLayoutController.mm | 4 +- .../Details/ASCollectionInternal.h | 18 +++++++ .../Details/ASCollectionInternal.m | 9 ++++ .../Details/ASRangeControllerBeta.mm | 1 + .../Sample.xcodeproj/project.pbxproj | 16 ------ .../Kittens/Sample.xcodeproj/project.pbxproj | 16 ------ .../Sample.xcodeproj/project.pbxproj | 16 ------ .../Sample.xcodeproj/project.pbxproj | 16 ------ 12 files changed, 105 insertions(+), 86 deletions(-) rename AsyncDisplayKit/{ASCollectionNode.m => ASCollectionNode.mm} (70%) create mode 100644 AsyncDisplayKit/Details/ASCollectionInternal.h create mode 100644 AsyncDisplayKit/Details/ASCollectionInternal.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index c622894086..f7b327ba60 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -112,8 +112,8 @@ 05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */; }; - 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */; }; + 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; + 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; 204C979E1B362CB3002B1083 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 204C979D1B362CB3002B1083 /* Default-568h@2x.png */; }; 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -466,6 +466,10 @@ DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; + DEC146B61C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; + DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; + DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; + DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; @@ -609,7 +613,7 @@ 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = ""; }; 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = ""; }; 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = ""; }; - 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionNode.m; sourceTree = ""; }; + 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = ""; }; 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = ""; }; 204C979D1B362CB3002B1083 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = ""; }; @@ -765,6 +769,8 @@ DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; + DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCollectionInternal.h; path = Details/ASCollectionInternal.h; sourceTree = ""; }; + DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.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 = ""; }; @@ -891,10 +897,12 @@ 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.m */, 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */, - 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.m */, + 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */, AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */, AC3C4A501A1139C100143C57 /* ASCollectionView.mm */, AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, + DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */, + DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */, 058D09D5195D050800B7D73C /* ASControlNode.h */, 058D09D6195D050800B7D73C /* ASControlNode.m */, DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, @@ -1367,6 +1375,7 @@ ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */, 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */, 257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */, + DEC146B61C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */, 058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */, @@ -1443,6 +1452,7 @@ 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, + DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutable.h in Headers */, 9CDC18CD1B910E12004965E2 /* ASLayoutablePrivate.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, @@ -1711,7 +1721,7 @@ AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, - 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, + 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, @@ -1724,6 +1734,7 @@ 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */, 25E327581C16819500A2170C /* ASPagerNode.m in Sources */, 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */, + DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */, 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */, 464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */, @@ -1845,7 +1856,7 @@ B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, - 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, + 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.m in Sources */, @@ -1855,6 +1866,7 @@ B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, 25E327591C16819500A2170C /* ASPagerNode.m in Sources */, B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, + DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */, 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.mm similarity index 70% rename from AsyncDisplayKit/ASCollectionNode.m rename to AsyncDisplayKit/ASCollectionNode.mm index d75d4ad58a..292d6d8851 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -7,7 +7,9 @@ // #import "ASCollectionNode.h" +#import "ASCollectionInternal.h" #import "ASDisplayNode+Subclasses.h" +#include @interface _ASCollectionPendingState : NSObject @property (weak, nonatomic) id delegate; @@ -17,14 +19,41 @@ @implementation _ASCollectionPendingState @end +#if 0 // This is not used yet, but will provide a way to avoid creating the view to set range values. +@implementation _ASCollectionPendingState +{ + std::vector _tuningParameters; +} + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + _tuningParameters = std::vector(ASLayoutRangeTypeCount); + return self; +} + +- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); + return _tuningParameters[rangeType]; +} + +- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType +{ + ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); + ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)"); + _tuningParameters[rangeType] = tuningParameters; +} + +@end +#endif + @interface ASCollectionNode () @property (nonatomic) _ASCollectionPendingState *pendingState; @end -@interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ownedByNode:(BOOL)ownedByNode; -@end - @implementation ASCollectionNode - (instancetype)init @@ -42,7 +71,10 @@ - (instancetype)_initWithCollectionView:(ASCollectionView *)collectionView { - if (self = [super initWithViewBlock:^UIView *{ return collectionView; }]) { + ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ return collectionView; }; + + if (self = [super initWithViewBlock:collectionViewBlock]) { + // ASCollectionView created directly by the app. Trigger -loadView to set up collectionNode pointer. __unused ASCollectionView *collectionView = [self view]; return self; } @@ -70,6 +102,7 @@ self.pendingState = nil; ASCollectionView *view = self.view; + view.collectionNode = self; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; } @@ -129,7 +162,7 @@ - (void)visibilityDidChange:(BOOL)isVisible { - + NSLog(@"%@ - visible: %d", self, isVisible); } - (void)clearContents @@ -148,12 +181,12 @@ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [self.view tuningParametersForRangeType:rangeType]; + return [self.view.rangeController tuningParametersForRangeType:rangeType]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - return [self.view setTuningParameters:tuningParameters forRangeType:rangeType]; + return [self.view.rangeController setTuningParameters:tuningParameters forRangeType:rangeType]; } - (void)reloadDataWithCompletion:(void (^)())completion diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index ec88645ea7..ac417335c8 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -15,6 +15,7 @@ #import @class ASCellNode; +@class ASCollectionNode; @protocol ASCollectionDataSource; @protocol ASCollectionDelegate; @protocol ASCollectionViewLayoutInspecting; @@ -43,6 +44,9 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; +// The corresponding ASCollectionNode, which exists even if directly allocating & handling the view class. +@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode; + @property (nonatomic, weak) id asyncDelegate; @property (nonatomic, weak) id asyncDataSource; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index d6f6f70908..4473e371a4 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -105,7 +105,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // We create a node so that logic related to appearance, memory management, etc can be located there // for both the node-based and view-based version of the table. // This also permits sharing logic with ASTableNode, as the superclass is not UIKit-controlled. -@property (nonatomic, retain) ASCollectionNode *strongCollectionNode; +@property (nonatomic, strong) ASCollectionNode *strongCollectionNode; + +// Always set, whether ASCollectionView is created directly or via ASCollectionNode. +@property (nonatomic, weak) ASCollectionNode *collectionNode; @end @@ -221,8 +224,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (_flowLayoutInspector == nil) { UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector"); - _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self - flowLayout:layout]; + _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; } return _flowLayoutInspector; } @@ -323,14 +325,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { - [_rangeController setTuningParameters:tuningParameters forRangeType:rangeType]; + [_collectionNode setTuningParameters:tuningParameters forRangeType:rangeType]; } - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType { - return [_rangeController tuningParametersForRangeType:rangeType]; + return [_collectionNode tuningParametersForRangeType:rangeType]; } +// These deprecated methods harken back from a time where only one range type existed. - (ASRangeTuningParameters)rangeTuningParameters { return [self tuningParametersForRangeType:ASLayoutRangeTypeDisplay]; @@ -762,6 +765,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - ASRangeControllerDataSource +- (ASRangeController *)rangeController +{ + return _rangeController; +} + - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 2a6678b1a9..22c951278d 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -7,10 +7,8 @@ */ #import "ASAbstractLayoutController.h" - -#include - #import "ASAssert.h" +#include @interface ASAbstractLayoutController () { std::vector _tuningParameters; diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.h b/AsyncDisplayKit/Details/ASCollectionInternal.h new file mode 100644 index 0000000000..9b72548ff1 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionInternal.h @@ -0,0 +1,18 @@ +// +// ASCollectionInternal.h +// AsyncDisplayKit +// +// Created by Scott Goodson on 1/1/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASCollectionView.h" +#import "ASCollectionNode.h" +#import "ASRangeController.h" + +@interface ASCollectionView () +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ownedByNode:(BOOL)ownedByNode; + +@property (nonatomic, weak, readwrite) ASCollectionNode *collectionNode; +@property (nonatomic, strong, readonly) ASRangeController *rangeController; +@end diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.m b/AsyncDisplayKit/Details/ASCollectionInternal.m new file mode 100644 index 0000000000..5acbc02b2e --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionInternal.m @@ -0,0 +1,9 @@ +// +// ASCollectionInternal.m +// AsyncDisplayKit +// +// Created by Scott Goodson on 1/1/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASCollectionInternal.h" diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 3160264c1d..ff823b01f0 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -124,6 +124,7 @@ NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, [visibleIndexPaths containsObject:indexPath], [displayIndexPaths containsObject:indexPath], [fetchDataIndexPaths containsObject:indexPath]); } */ + _rangeIsValid = YES; _queuedRangeUpdate = NO; diff --git a/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj index a93761d93a..74b4210418 100644 --- a/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj +++ b/examples/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj @@ -130,7 +130,6 @@ AC3C4A5B1A11F47200143C57 /* Frameworks */, AC3C4A5C1A11F47200143C57 /* Resources */, A6902C454C7661D0D277AC62 /* Copy Pods Resources */, - EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -201,21 +200,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index becf26110d..ddfd884074 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 54296444B3B4D82560F3906E /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,21 +184,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 54296444B3B4D82560F3906E /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/Multiplex/Sample.xcodeproj/project.pbxproj b/examples/Multiplex/Sample.xcodeproj/project.pbxproj index 7408b333ba..1383d38552 100644 --- a/examples/Multiplex/Sample.xcodeproj/project.pbxproj +++ b/examples/Multiplex/Sample.xcodeproj/project.pbxproj @@ -123,7 +123,6 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 93B7780A33739EF25F20366B /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -180,21 +179,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 93B7780A33739EF25F20366B /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/SynchronousKittens/Sample.xcodeproj/project.pbxproj b/examples/SynchronousKittens/Sample.xcodeproj/project.pbxproj index a8ef05da55..ddfd884074 100644 --- a/examples/SynchronousKittens/Sample.xcodeproj/project.pbxproj +++ b/examples/SynchronousKittens/Sample.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - ACCB3408566E7626721EF2D5 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,21 +184,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - ACCB3408566E7626721EF2D5 /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 688e8d27de7ec14b53c55b8981230193cd3f665e Mon Sep 17 00:00:00 2001 From: Rajinder Ramgarhia Date: Sun, 3 Jan 2016 11:02:33 -0500 Subject: [PATCH 26/39] Add comment explaning the change --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ---------------- AsyncDisplayKit/ASControlNode.m | 1 + 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index d970da04e2..c622894086 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1547,7 +1547,6 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - 2AC85249A2221047409FABBB /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1647,21 +1646,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2AC85249A2221047409FABBB /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 2E61B6A0DB0F436A9DDBE86F /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 160e2867d8..0e2ab78d5e 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -339,6 +339,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ (ASControlNodeEvent controlEvent) { + // Use a copy to itereate, the action perform could call remove causing a mutation crash. NSMapTable *eventDispatchTable = [[_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)] copy]; // For each target interested in this event... From a509b7523f4ba42bcd3b0735ba1051431a2d15a7 Mon Sep 17 00:00:00 2001 From: Rahul Malik Date: Sun, 3 Jan 2016 17:45:51 -0800 Subject: [PATCH 27/39] Update interface of ASViewController to use lightweight generics. This allows subclasses of ASViewController to specify the type of ASDisplayNode it contains which allows for stronger type-checking and auto-completion against the specified type. Example Subclass Declaration: @interface MyASViewController : ASViewController @end --- AsyncDisplayKit/ASViewController.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index 74c674f6e0..d7286272b8 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -11,11 +11,11 @@ NS_ASSUME_NONNULL_BEGIN -@interface ASViewController : UIViewController +@interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController -- (instancetype)initWithNode:(ASDisplayNode *)node NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; -@property (nonatomic, strong, readonly) ASDisplayNode *node; +@property (nonatomic, strong, readonly) DisplayNodeType node; /** * @abstract Passthrough property to the the .interfaceState of the node. From 984fe4399736fa4d4807ec87e8c7a522085454e1 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 3 Jan 2016 19:14:07 -0800 Subject: [PATCH 28/39] [ASRangeController] Inspect delegate's ASInterfaceState to delay preloading beyond viewport until visible. --- AsyncDisplayKit/ASCollectionNode.mm | 12 +- AsyncDisplayKit/ASCollectionView.mm | 26 ++++ AsyncDisplayKit/ASDisplayNode.h | 2 - AsyncDisplayKit/ASTableNode.m | 16 ++- AsyncDisplayKit/ASTableView.mm | 29 +++++ AsyncDisplayKit/ASTableViewInternal.h | 1 + AsyncDisplayKit/Details/ASRangeController.h | 11 ++ .../Details/ASRangeControllerBeta.mm | 118 +++++++++++++----- .../Private/ASDisplayNode+FrameworkPrivate.h | 7 ++ .../Private/ASDisplayNodeInternal.h | 8 -- 10 files changed, 181 insertions(+), 49 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 292d6d8851..f184a969a2 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -9,6 +9,7 @@ #import "ASCollectionNode.h" #import "ASCollectionInternal.h" #import "ASDisplayNode+Subclasses.h" +#import "ASRangeController.h" #include @interface _ASCollectionPendingState : NSObject @@ -97,12 +98,12 @@ { [super didLoad]; + ASCollectionView *view = self.view; + view.collectionNode = self; + if (_pendingState) { _ASCollectionPendingState *pendingState = _pendingState; - self.pendingState = nil; - - ASCollectionView *view = self.view; - view.collectionNode = self; + self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; } @@ -160,10 +161,13 @@ return (ASCollectionView *)[super view]; } +#if RangeControllerLoggingEnabled - (void)visibilityDidChange:(BOOL)isVisible { + [super visibilityDidChange:isVisible]; NSLog(@"%@ - visible: %d", self, isVisible); } +#endif - (void)clearContents { diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 4473e371a4..f42ec5b6b0 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -782,6 +782,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return self.bounds.size; } +- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController +{ + return self.collectionNode.interfaceState; +} + - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths { return [_dataController nodesAtIndexPaths:indexPaths]; @@ -944,6 +949,27 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } +#pragma mark - _ASDisplayView behavior substitutions +// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. +// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. +- (void)willMoveToWindow:(UIWindow *)newWindow +{ + BOOL visible = (newWindow != nil); + ASDisplayNode *node = self.collectionNode; + if (visible && !node.inHierarchy) { + [node __enterHierarchy]; + } +} + +- (void)didMoveToWindow +{ + BOOL visible = (self.window != nil); + ASDisplayNode *node = self.collectionNode; + if (!visible && node.inHierarchy) { + [node __exitHierarchy]; + } +} + #pragma mark - UICollectionView dead-end intercepts #if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting. diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 3e2a32fb44..a38f093ca2 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -66,8 +66,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) ASInterfaceStateInHierarchy = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay | ASInterfaceStateVisible, }; - - /** * An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view * hierarchy off the main thread, and could do rendering off the main thread as well. diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index e81366ed99..f81623aeb7 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -9,6 +9,7 @@ #import "ASFlowLayoutController.h" #import "ASTableViewInternal.h" #import "ASDisplayNode+Subclasses.h" +#import "ASRangeController.h" @interface _ASTablePendingState : NSObject @property (weak, nonatomic) id delegate; @@ -63,11 +64,12 @@ { [super didLoad]; + ASTableView *view = self.view; + view.tableNode = self; + if (_pendingState) { _ASTablePendingState *pendingState = _pendingState; - self.pendingState = nil; - - ASTableView *view = self.view; + self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; } @@ -125,6 +127,14 @@ return (ASTableView *)[super view]; } +#if RangeControllerLoggingEnabled +- (void)visibilityDidChange:(BOOL)isVisible +{ + [super visibilityDidChange:isVisible]; + NSLog(@"%@ - visible: %d", self, isVisible); +} +#endif + - (void)clearContents { [super clearContents]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 679a00d46a..7632a7c63d 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -121,6 +121,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // This also permits sharing logic with ASCollectionNode, as the superclass is not UIKit-controlled. @property (nonatomic, retain) ASTableNode *strongTableNode; +// Always set, whether ASCollectionView is created directly or via ASCollectionNode. +@property (nonatomic, weak) ASTableNode *tableNode; + @end @implementation ASTableView @@ -700,6 +703,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return self.bounds.size; } +- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController +{ + return self.tableNode.interfaceState; +} + #pragma mark - ASRangeControllerDelegate - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController @@ -943,4 +951,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +#pragma mark - _ASDisplayView behavior substitutions +// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. +// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. +- (void)willMoveToWindow:(UIWindow *)newWindow +{ + BOOL visible = (newWindow != nil); + ASDisplayNode *node = self.tableNode; + if (visible && !node.inHierarchy) { + [node __enterHierarchy]; + } +} + +- (void)didMoveToWindow +{ + BOOL visible = (self.window != nil); + ASDisplayNode *node = self.tableNode; + if (!visible && node.inHierarchy) { + [node __exitHierarchy]; + } +} + @end diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h index 02eb062d46..cc93c44a84 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -13,6 +13,7 @@ @interface ASTableView (Internal) @property (nonatomic, retain, readonly) ASDataController *dataController; +@property (nonatomic, weak, readwrite) ASTableNode *tableNode; /** * Initializer. diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index 6ffc16377d..c8dee77e11 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -12,6 +12,8 @@ #import #import +#define RangeControllerLoggingEnabled 0 + NS_ASSUME_NONNULL_BEGIN @protocol ASRangeControllerDataSource; @@ -102,6 +104,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController; +/** + * @param rangeController Sender. + * + * @returns the ASInterfaceState of the node that this controller is powering. This allows nested range controllers + * to collaborate with one another, as an outer controller may set bits in .interfaceState such as Visible. + * If this controller is an orthogonally scrolling element, it waits until it is visible to preload outside the viewport. + */ +- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController; + - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths; - (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath; diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index ff823b01f0..979fdf320d 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -17,6 +17,21 @@ #import "ASInternalHelpers.h" #import "ASDisplayNode+FrameworkPrivate.h" +extern BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState) +{ + return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible); +} + +extern BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState) +{ + return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay); +} + +extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) +{ + return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData); +} + @interface ASRangeControllerBeta () { BOOL _rangeIsValid; @@ -79,52 +94,91 @@ [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; } - NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData]; - NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay]; + ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; + NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; - - //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]; - [allIndexPaths unionSet:displayIndexPaths]; - [allIndexPaths unionSet:visibleIndexPaths]; +#if RangeControllerLoggingEnabled NSMutableArray *modified = [NSMutableArray array]; +#endif - for (NSIndexPath *indexPath in allIndexPaths) { - // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. - // For consistency, make sure each node knows that it should measure itself if something changes. - ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; + if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { + // If we are already visible, get busy! Better get started on preloading before the user scrolls more... + NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData]; + NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay]; + + // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. + NSMutableSet *allIndexPaths = [fetchDataIndexPaths mutableCopy]; + [allIndexPaths unionSet:displayIndexPaths]; + [allIndexPaths unionSet:visibleIndexPaths]; - if ([fetchDataIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateFetchData; - } - if ([displayIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateDisplay; - } - if ([visibleIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateVisible; + for (NSIndexPath *indexPath in allIndexPaths) { + // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. + // For consistency, make sure each node knows that it should measure itself if something changes. + ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; + + if ([fetchDataIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateFetchData; + } + if ([displayIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateDisplay; + } + if ([visibleIndexPaths containsObject:indexPath]) { + interfaceState |= ASInterfaceStateVisible; + } + + ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); + // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. + if (node.interfaceState != interfaceState) { +#if RangeControllerLoggingEnabled + [modified addObject:indexPath]; +#endif + [node recursivelySetInterfaceState:interfaceState]; + } } + } else { + // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the + // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. + // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { - [modified addObject:indexPath]; - [node recursivelySetInterfaceState:interfaceState]; + for (NSIndexPath *indexPath in visibleIndexPaths) { + // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport", + // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above. + ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay; + + ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); + // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. + if (node.interfaceState != interfaceState) { +#if RangeControllerLoggingEnabled + [modified addObject:indexPath]; +#endif + [node recursivelySetInterfaceState:interfaceState]; + } } } -/* +#if RangeControllerLoggingEnabled + NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; + BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; + NSLog(@"visible sets are equal: %d", setsAreEqual); + if (!setsAreEqual) { + NSLog(@"standard: %@", visibleIndexPaths); + NSLog(@"custom: %@", visibleNodePathsSet); + } + [modified sortUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in modified) { - NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, [visibleIndexPaths containsObject:indexPath], [displayIndexPaths containsObject:indexPath], [fetchDataIndexPaths containsObject:indexPath]); + ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; + ASInterfaceState interfaceState = node.interfaceState; + BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); + BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); + BOOL inFetchData = ASInterfaceStateIncludesFetchData(interfaceState); + NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData); } -*/ - +#endif _rangeIsValid = YES; _queuedRangeUpdate = NO; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 896e82668f..d3a5e711af 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -66,6 +66,13 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) - (void)enterHierarchyState:(ASHierarchyState)hierarchyState; - (void)exitHierarchyState:(ASHierarchyState)hierarchyState; +// Changed before calling willEnterHierarchy / didExitHierarchy. +@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy; +// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents +- (void)__enterHierarchy; +// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents +- (void)__exitHierarchy; + /** * @abstract Returns the Hierarchy State of the node. * diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 1636e5a175..c1f752002d 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -132,20 +132,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; -// Changed before calling willEnterHierarchy / didExitHierarchy. -@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy; - // Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. - (BOOL)__visibilityNotificationsDisabled; - (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; - (void)__incrementVisibilityNotificationsDisabled; - (void)__decrementVisibilityNotificationsDisabled; -// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents -- (void)__enterHierarchy; -// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents -- (void)__exitHierarchy; - // Helper method to summarize whether or not the node run through the display process - (BOOL)__implementsDisplay; From 6bb51063f9ae4658a5869ba282f9ca2d42d750e6 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 5 Jan 2016 17:07:44 -0800 Subject: [PATCH 29/39] Replace ASButtonState with ASControlState --- AsyncDisplayKit/ASButtonNode.h | 14 +++------ AsyncDisplayKit/ASButtonNode.mm | 52 +++++++++++++++++++++++---------- AsyncDisplayKit/ASControlNode.h | 13 +++++++++ AsyncDisplayKit/ASControlNode.m | 4 +++ 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.h b/AsyncDisplayKit/ASButtonNode.h index 8156496279..588319b4b6 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/AsyncDisplayKit/ASButtonNode.h @@ -9,12 +9,6 @@ #import #import -typedef enum : NSUInteger { - ASButtonStateNormal, - ASButtonStateHighlighted, - ASButtonStateDisabled, -} ASButtonState; - @interface ASButtonNode : ASControlNode @property (nonatomic, readonly) ASTextNode *titleNode; @@ -43,10 +37,10 @@ typedef enum : NSUInteger { @property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment; -- (NSAttributedString *)attributedTitleForState:(ASButtonState)state; -- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state; +- (NSAttributedString *)attributedTitleForState:(ASControlState)state; +- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state; -- (UIImage *)imageForState:(ASButtonState)state; -- (void)setImage:(UIImage *)image forState:(ASButtonState)state; +- (UIImage *)imageForState:(ASControlState)state; +- (void)setImage:(UIImage *)image forState:(ASControlState)state; @end diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 0d4004411c..6d814a6804 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -17,10 +17,12 @@ NSAttributedString *_normalAttributedTitle; NSAttributedString *_highlightedAttributedTitle; + NSAttributedString *_seletedAttributedTitle; NSAttributedString *_disabledAttributedTitle; UIImage *_normalImage; UIImage *_highlightedImage; + UIImage *_selectedImage; UIImage *_disabledImage; } @@ -66,6 +68,8 @@ newImage = _disabledImage; } else if (self.highlighted && _highlightedImage) { newImage = _highlightedImage; + } else if (self.selected && _selectedImage) { + newImage = _selectedImage; } else { newImage = _normalImage; } @@ -84,6 +88,8 @@ newTitle = _disabledAttributedTitle; } else if (self.highlighted && _highlightedAttributedTitle) { newTitle = _highlightedAttributedTitle; + } else if (self.selected && _seletedAttributedTitle) { + newTitle = _seletedAttributedTitle; } else { newTitle = _normalAttributedTitle; } @@ -126,68 +132,82 @@ [self setNeedsLayout]; } -- (NSAttributedString *)attributedTitleForState:(ASButtonState)state +- (NSAttributedString *)attributedTitleForState:(ASControlState)state { ASDN::MutexLocker l(_propertyLock); switch (state) { - case ASButtonStateNormal: + case ASControlStateNormal: return _normalAttributedTitle; - case ASButtonStateHighlighted: + case ASControlStateHighlighted: return _highlightedAttributedTitle; - case ASButtonStateDisabled: + case ASControlStateSelected: + return _seletedAttributedTitle; + + case ASControlStateDisabled: return _disabledAttributedTitle; } } -- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASButtonState)state +- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state { ASDN::MutexLocker l(_propertyLock); switch (state) { - case ASButtonStateNormal: + case ASControlStateNormal: _normalAttributedTitle = [title copy]; break; - case ASButtonStateHighlighted: + case ASControlStateHighlighted: _highlightedAttributedTitle = [title copy]; break; - case ASButtonStateDisabled: + case ASControlStateSelected: + _seletedAttributedTitle = [title copy]; + break; + + case ASControlStateDisabled: _disabledAttributedTitle = [title copy]; break; } [self updateTitle]; } -- (UIImage *)imageForState:(ASButtonState)state +- (UIImage *)imageForState:(ASControlState)state { ASDN::MutexLocker l(_propertyLock); switch (state) { - case ASButtonStateNormal: + case ASControlStateNormal: return _normalImage; - case ASButtonStateHighlighted: + case ASControlStateHighlighted: return _highlightedImage; - case ASButtonStateDisabled: + case ASControlStateSelected: + return _selectedImage; + + case ASControlStateDisabled: return _disabledImage; } } -- (void)setImage:(UIImage *)image forState:(ASButtonState)state +- (void)setImage:(UIImage *)image forState:(ASControlState)state { ASDN::MutexLocker l(_propertyLock); switch (state) { - case ASButtonStateNormal: + case ASControlStateNormal: _normalImage = image; break; - case ASButtonStateHighlighted: + case ASControlStateHighlighted: _highlightedImage = image; break; - case ASButtonStateDisabled: + case ASControlStateSelected: + _selectedImage = image; + break; + + case ASControlStateDisabled: _disabledImage = image; break; } diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index 28a40ad51e..ad7049c261 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -34,6 +34,13 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent) ASControlNodeEventAllEvents = 0xFFFFFFFF }; +typedef NS_OPTIONS(NSUInteger, ASControlState) { + ASControlStateNormal = 0, + ASControlStateHighlighted = 1 << 0, // used when ASControlNode isHighlighted is set + ASControlStateDisabled = 1 << 1, + ASControlStateSelected = 1 << 2, // used when ASControlNode isSeleted is set + ASControlStateReserved = 0xFF000000 // flags reserved for internal framework use +}; /** @abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages. @@ -55,6 +62,12 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent) */ @property (nonatomic, readonly, assign, getter=isHighlighted) BOOL highlighted; +/** + @abstract Indicates whether or not the receiver is highlighted. + @discussion This is set automatically when the receiver is tapped. + */ +@property (nonatomic, assign, getter=isSeleted) BOOL selected; + #pragma mark - Tracking Touches /** @abstract Indicates whether or not the receiver is currently tracking touches related to an event. diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 0e2ab78d5e..6a9c8bd250 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -186,6 +186,10 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Send the appropriate touch-up control event. CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); + + if (touchUpIsInsideExpandedBounds) { + self.selected = !self.selected; + } [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) withEvent:event]; From 82c098b39d1741c168996ab40f533bce4d12f582 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 5 Jan 2016 17:35:30 -0800 Subject: [PATCH 30/39] Fixed typo --- AsyncDisplayKit/ASControlNode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index ad7049c261..72e1c52802 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -66,7 +66,7 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) { @abstract Indicates whether or not the receiver is highlighted. @discussion This is set automatically when the receiver is tapped. */ -@property (nonatomic, assign, getter=isSeleted) BOOL selected; +@property (nonatomic, assign, getter=isSelected) BOOL selected; #pragma mark - Tracking Touches /** From 4f67050b9bd4d8f066c26ebd08854f8a2c25475f Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 5 Jan 2016 18:04:26 -0800 Subject: [PATCH 31/39] Fix another typo, fix build failure --- AsyncDisplayKit/ASButtonNode.mm | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 6d814a6804..4e7330c5ba 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -17,7 +17,7 @@ NSAttributedString *_normalAttributedTitle; NSAttributedString *_highlightedAttributedTitle; - NSAttributedString *_seletedAttributedTitle; + NSAttributedString *_selectedAttributedTitle; NSAttributedString *_disabledAttributedTitle; UIImage *_normalImage; @@ -88,8 +88,8 @@ newTitle = _disabledAttributedTitle; } else if (self.highlighted && _highlightedAttributedTitle) { newTitle = _highlightedAttributedTitle; - } else if (self.selected && _seletedAttributedTitle) { - newTitle = _seletedAttributedTitle; + } else if (self.selected && _selectedAttributedTitle) { + newTitle = _selectedAttributedTitle; } else { newTitle = _normalAttributedTitle; } @@ -143,10 +143,13 @@ return _highlightedAttributedTitle; case ASControlStateSelected: - return _seletedAttributedTitle; + return _selectedAttributedTitle; case ASControlStateDisabled: return _disabledAttributedTitle; + + default: + return _normalAttributedTitle; } } @@ -163,12 +166,15 @@ break; case ASControlStateSelected: - _seletedAttributedTitle = [title copy]; + _selectedAttributedTitle = [title copy]; break; case ASControlStateDisabled: _disabledAttributedTitle = [title copy]; break; + + default: + break; } [self updateTitle]; } @@ -188,6 +194,9 @@ case ASControlStateDisabled: return _disabledImage; + + default: + return _normalImage; } } @@ -210,6 +219,9 @@ case ASControlStateDisabled: _disabledImage = image; break; + + default: + break; } [self updateImage]; } From 6a4c95344378b59f8ed7e25cffa11026abfb3b35 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 5 Jan 2016 21:20:52 -0800 Subject: [PATCH 32/39] fix build failure --- examples/Multiplex/Sample/ScreenNode.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Multiplex/Sample/ScreenNode.m b/examples/Multiplex/Sample/ScreenNode.m index b3e661079a..b73368a03f 100644 --- a/examples/Multiplex/Sample/ScreenNode.m +++ b/examples/Multiplex/Sample/ScreenNode.m @@ -60,7 +60,7 @@ NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]}; NSAttributedString *string = [[NSAttributedString alloc] initWithString:text attributes:attributes]; - [_buttonNode setAttributedTitle:string forState:ASButtonStateNormal]; + [_buttonNode setAttributedTitle:string forState:ASControlStateNormal]; [self setNeedsLayout]; } From d1c3894dbb032bd96c358b6f7d9f648140019d10 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 5 Jan 2016 23:17:54 -0800 Subject: [PATCH 33/39] [ASRangeControllerBeta] Add tracking of all index paths that have .interfaceState set, ensure updating. --- AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 979fdf320d..9eecbcf9e9 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -37,6 +37,7 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) BOOL _rangeIsValid; BOOL _queuedRangeUpdate; ASScrollDirection _scrollDirection; + NSMutableSet *_allPreviousIndexPaths; } @end @@ -112,6 +113,12 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) [allIndexPaths unionSet:displayIndexPaths]; [allIndexPaths unionSet:visibleIndexPaths]; + // Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any + // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic + // scroll or major main thread stall could cause entirely disjoint sets, but we must visit all. + [allIndexPaths unionSet:_allPreviousIndexPaths]; + _allPreviousIndexPaths = allIndexPaths; + for (NSIndexPath *indexPath in allIndexPaths) { // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. // For consistency, make sure each node knows that it should measure itself if something changes. From bbfd08829f310125f18a4ac0719b0d21164e0eb9 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Tue, 5 Jan 2016 23:29:11 -0800 Subject: [PATCH 34/39] Switch Tests to use ASRangeControllerBeta. Use NSSet instead of NSMutableSet. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ++++++++++------ AsyncDisplayKit/Details/ASRangeControllerBeta.mm | 5 +++-- .../{AppDelegate.m => AppDelegate.mm} | 6 +++++- 3 files changed, 18 insertions(+), 9 deletions(-) rename AsyncDisplayKitTestHost/{AppDelegate.m => AppDelegate.mm} (70%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f7b327ba60..4ce31a9543 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -40,7 +40,7 @@ 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */; }; 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; 057D02C41AC0A66700C7AC3C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C31AC0A66700C7AC3C /* main.m */; }; - 057D02C71AC0A66700C7AC3C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.m */; }; + 057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */; }; 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */; }; 058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; @@ -539,7 +539,7 @@ 057D02C21AC0A66700C7AC3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 057D02C31AC0A66700C7AC3C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 057D02C51AC0A66700C7AC3C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 057D02C61AC0A66700C7AC3C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = ""; }; 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEditableTextNode.h; sourceTree = ""; }; 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASEditableTextNode.mm; sourceTree = ""; }; 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -833,7 +833,7 @@ children = ( 204C979D1B362CB3002B1083 /* Default-568h@2x.png */, 057D02C51AC0A66700C7AC3C /* AppDelegate.h */, - 057D02C61AC0A66700C7AC3C /* AppDelegate.m */, + 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */, 057D02C11AC0A66700C7AC3C /* Supporting Files */, ); name = AsyncDisplayKitTestHost; @@ -1693,7 +1693,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 057D02C71AC0A66700C7AC3C /* AppDelegate.m in Sources */, + 057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */, 057D02C41AC0A66700C7AC3C /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1966,7 +1966,7 @@ "$(inherited)", ); INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1983,7 +1983,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = NO; INFOPLIST_FILE = AsyncDisplayKitTestHost/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2075,6 +2075,7 @@ GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2095,6 +2096,7 @@ GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2125,6 +2127,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; WRAPPER_EXTENSION = xctest; @@ -2152,6 +2155,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; WRAPPER_EXTENSION = xctest; diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 9eecbcf9e9..bc98031e67 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -37,7 +37,7 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) BOOL _rangeIsValid; BOOL _queuedRangeUpdate; ASScrollDirection _scrollDirection; - NSMutableSet *_allPreviousIndexPaths; + NSSet *_allPreviousIndexPaths; } @end @@ -116,8 +116,9 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) // Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic // scroll or major main thread stall could cause entirely disjoint sets, but we must visit all. + NSSet *allCurrentIndexPaths = [allIndexPaths copy]; [allIndexPaths unionSet:_allPreviousIndexPaths]; - _allPreviousIndexPaths = allIndexPaths; + _allPreviousIndexPaths = allCurrentIndexPaths; for (NSIndexPath *indexPath in allIndexPaths) { // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. diff --git a/AsyncDisplayKitTestHost/AppDelegate.m b/AsyncDisplayKitTestHost/AppDelegate.mm similarity index 70% rename from AsyncDisplayKitTestHost/AppDelegate.m rename to AsyncDisplayKitTestHost/AppDelegate.mm index c5b815b49b..02b188967c 100644 --- a/AsyncDisplayKitTestHost/AppDelegate.m +++ b/AsyncDisplayKitTestHost/AppDelegate.mm @@ -7,10 +7,14 @@ */ #import "AppDelegate.h" +#import +#import @implementation AppDelegate -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + [ASDisplayNode setShouldUseNewRenderingRange:YES]; return YES; } From 4435405cbbedc9e8fb10e826226638d4af180f8f Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Wed, 6 Jan 2016 12:17:38 -0800 Subject: [PATCH 35/39] Make ASControlState imitate UIControlState - selected state isn't implicitly controlled by ASControlNode anymore - highlighted state can be overriden by users - Decouple ASButtonNode's state from control events --- AsyncDisplayKit/ASButtonNode.mm | 23 ++++++++++++++++++----- AsyncDisplayKit/ASControlNode.h | 2 +- AsyncDisplayKit/ASControlNode.m | 5 ----- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 4e7330c5ba..55ed7d6123 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -47,16 +47,29 @@ [self addSubnode:_titleNode]; [self addSubnode:_imageNode]; - - [self addTarget:self action:@selector(controlEventUpdated:) forControlEvents:ASControlNodeEventAllEvents]; } return self; } -- (void)controlEventUpdated:(ASControlNode *)node +- (void)setEnabled:(BOOL)enabled { - [self updateImage]; - [self updateTitle]; + [super setEnabled:enabled]; + [self updateImage]; + [self updateTitle]; +} + +- (void)setHighlighted:(BOOL)highlighted +{ + [super setHighlighted:highlighted]; + [self updateImage]; + [self updateTitle]; +} + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + [self updateImage]; + [self updateTitle]; } - (void)updateImage diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index 72e1c52802..9f5c37e343 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -60,7 +60,7 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) { @abstract Indicates whether or not the receiver is highlighted. @discussion This is set automatically when the there is a touch inside the control and removed on exit or touch up. This is different from touchInside in that it includes an area around the control, rather than just for touches inside the control. */ -@property (nonatomic, readonly, assign, getter=isHighlighted) BOOL highlighted; +@property (nonatomic, assign, getter=isHighlighted) BOOL highlighted; /** @abstract Indicates whether or not the receiver is highlighted. diff --git a/AsyncDisplayKit/ASControlNode.m b/AsyncDisplayKit/ASControlNode.m index 6a9c8bd250..66da4e5755 100644 --- a/AsyncDisplayKit/ASControlNode.m +++ b/AsyncDisplayKit/ASControlNode.m @@ -46,7 +46,6 @@ } // Read-write overrides. -@property (nonatomic, readwrite, assign, getter=isHighlighted) BOOL highlighted; @property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking; @property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside; @@ -186,10 +185,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Send the appropriate touch-up control event. CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - - if (touchUpIsInsideExpandedBounds) { - self.selected = !self.selected; - } [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) withEvent:event]; From a36a955eb06835f7a1774a301c0c7f79b21408e6 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Wed, 6 Jan 2016 13:09:59 -0800 Subject: [PATCH 36/39] fix indentation --- AsyncDisplayKit/ASButtonNode.mm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 55ed7d6123..500d86271d 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -53,23 +53,23 @@ - (void)setEnabled:(BOOL)enabled { - [super setEnabled:enabled]; - [self updateImage]; - [self updateTitle]; + [super setEnabled:enabled]; + [self updateImage]; + [self updateTitle]; } - (void)setHighlighted:(BOOL)highlighted { - [super setHighlighted:highlighted]; - [self updateImage]; - [self updateTitle]; + [super setHighlighted:highlighted]; + [self updateImage]; + [self updateTitle]; } - (void)setSelected:(BOOL)selected { - [super setSelected:selected]; - [self updateImage]; - [self updateTitle]; + [super setSelected:selected]; + [self updateImage]; + [self updateTitle]; } - (void)updateImage From d45db5ac326dd415b4d528fbff3ac29de782d674 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 9 Jan 2016 20:44:24 -0800 Subject: [PATCH 37/39] Disable creation of backing ASTable/CollectionNode for the *View varients (retain cycle). --- AsyncDisplayKit/ASCollectionView.mm | 10 +++++++++- AsyncDisplayKit/ASTableNode.m | 6 ++++-- AsyncDisplayKit/ASTableView.mm | 12 ++++++++++-- AsyncDisplayKit/Details/ASRangeController.h | 2 ++ examples/ASTableViewStressTest/Sample/AppDelegate.m | 5 +++++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index f42ec5b6b0..4124f1a03b 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -150,7 +150,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; if (!ownedByNode) { // See commentary at the definition of .strongCollectionNode for why we create an ASCollectionNode. - ASCollectionNode *collectionNode = [[ASCollectionNode alloc] _initWithCollectionView:self]; + // FIXME: The _view pointer of the node retains us, but the node will die immediately if we don't + // retain it. At the moment there isn't a great solution to this, so we can't yet move our core + // logic to ASCollectionNode (required to have a shared superclass with ASTable*). + ASCollectionNode *collectionNode = nil; //[[ASCollectionNode alloc] _initWithCollectionView:self]; self.strongCollectionNode = collectionNode; } @@ -349,6 +352,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [[_dataController nodeAtIndexPath:indexPath] calculatedSize]; } +- (NSArray *> *)completedNodes +{ + return [_dataController completedNodes]; +} + - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath { return [_dataController nodeAtIndexPath:indexPath]; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index f81623aeb7..a54f0bf11e 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -31,8 +31,10 @@ - (instancetype)_initWithTableView:(ASTableView *)tableView { - if (self = [super initWithViewBlock:^UIView *{ return tableView; }]) { - __unused ASTableView *tableView = [self view]; + // Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us. + ASTableView * __weak weakTableView = tableView; + if (self = [super initWithViewBlock:^UIView *{ return weakTableView; }]) { + __unused __weak ASTableView *view = [self view]; return self; } return nil; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 7632a7c63d..6bb97782f9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -198,14 +198,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } if (!dataControllerClass) { - dataControllerClass = [self.class dataControllerClass]; + dataControllerClass = [[self class] dataControllerClass]; } [self configureWithDataControllerClass:dataControllerClass]; if (!ownedByNode) { // See commentary at the definition of .strongTableNode for why we create an ASTableNode. - ASTableNode *tableNode = [[ASTableNode alloc] _initWithTableView:self]; + // FIXME: The _view pointer of the node retains us, but the node will die immediately if we don't + // retain it. At the moment there isn't a great solution to this, so we can't yet move our core + // logic to ASTableNode (required to have a shared superclass with ASCollection*). + ASTableNode *tableNode = nil; //[[ASTableNode alloc] _initWithTableView:self]; self.strongTableNode = tableNode; } @@ -333,6 +336,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; } +- (NSArray *> *)completedNodes +{ + return [_dataController completedNodes]; +} + - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath { return [_dataController nodeAtIndexPath:indexPath]; diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index c8dee77e11..74061f8806 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -117,6 +117,8 @@ NS_ASSUME_NONNULL_BEGIN - (ASDisplayNode *)rangeController:(ASRangeController *)rangeController nodeAtIndexPath:(NSIndexPath *)indexPath; +- (NSArray *> *)completedNodes; + @end /** diff --git a/examples/ASTableViewStressTest/Sample/AppDelegate.m b/examples/ASTableViewStressTest/Sample/AppDelegate.m index a8d611608c..4bc15faea9 100644 --- a/examples/ASTableViewStressTest/Sample/AppDelegate.m +++ b/examples/ASTableViewStressTest/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] init]; From 0feaa2a36856e6d170b81f65c94a3b1af812d30a Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 10 Jan 2016 02:33:34 -0800 Subject: [PATCH 38/39] Improvements to the efficiency of recursivelySetInterfaceState: and the beta range controller. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 13 ++++ .../xcshareddata/WorkspaceSettings.xcsettings | 8 --- AsyncDisplayKit/ASDisplayNode.mm | 65 +++++++++++++++---- AsyncDisplayKit/ASDisplayNodeExtras.h | 17 +++++ .../Details/ASRangeControllerBeta.mm | 15 ----- AsyncDisplayKit/Details/_ASDisplayLayer.mm | 2 + AsyncDisplayKit/Layout/ASLayoutSpec.mm | 19 ++++-- .../Private/ASDisplayNode+UIViewBridge.mm | 9 ++- .../Private/ASDisplayNodeInternal.h | 2 +- 9 files changed, 103 insertions(+), 47 deletions(-) delete mode 100644 AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4ce31a9543..8437e36896 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -495,6 +495,13 @@ remoteGlobalIDString = 058D09AB195D04C000B7D73C; remoteInfo = AsyncDisplayKit; }; + DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 058D09A4195D04C000B7D73C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 058D09AB195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1524,6 +1531,7 @@ buildRules = ( ); dependencies = ( + DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */, ); name = AsyncDisplayKitTestHost; productName = AsyncDisplayKitTestHost; @@ -1939,6 +1947,11 @@ target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */; targetProxy = 058D09C2195D04C000B7D73C /* PBXContainerItemProxy */; }; + DEACA2B21C425DC400FA9DDF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 058D09AB195D04C000B7D73C /* AsyncDisplayKit */; + targetProxy = DEACA2B11C425DC400FA9DDF /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ diff --git a/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 08de0be8d3..0000000000 --- a/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - - - diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 97c2be98ed..1346f7e910 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -198,12 +198,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return [_ASDisplayLayer class]; } -+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node ++ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node { ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert([ASDisplayNode shouldUseNewRenderingRange], @"+scheduleNodeForRecursiveDisplay: should never be called without the new rendering range enabled"); static NSMutableSet *nodesToDisplay = nil; static BOOL displayScheduled = NO; static ASDN::RecursiveMutex displaySchedulerLock; + { ASDN::MutexLocker l(displaySchedulerLock); if (!nodesToDisplay) { @@ -211,6 +213,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } [nodesToDisplay addObject:node]; } + if (!displayScheduled) { displayScheduled = YES; // It's essenital that any layout pass that is scheduled during the current @@ -1557,13 +1560,11 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay { - ASDN::MutexLocker l(_propertyLock); _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; } - (BOOL)shouldBypassEnsureDisplay { - ASDN::MutexLocker l(_propertyLock); return _flags.shouldBypassEnsureDisplay; } @@ -1612,7 +1613,7 @@ static BOOL ShouldUseNewRenderingRange = NO; - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASDisplayNodeAssertThreadAffinity(self); - return [ASLayoutSpec new]; + return nil; } - (ASLayout *)calculatedLayout @@ -1772,7 +1773,7 @@ static BOOL ShouldUseNewRenderingRange = NO; oldState = _interfaceState; _interfaceState = newState; } - + if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { // Trigger asynchronous measurement if it is not already cached or being calculated. } @@ -1782,8 +1783,11 @@ static BOOL ShouldUseNewRenderingRange = NO; // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. // Entered or exited data loading state. - if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { - if (newState & ASInterfaceStateFetchData) { + BOOL nowFetchData = ASInterfaceStateIncludesFetchData(newState); + BOOL wasFetchData = ASInterfaceStateIncludesFetchData(oldState); + + if (nowFetchData != wasFetchData) { + if (nowFetchData) { [self fetchData]; } else { if ([self supportsRangeManagedInterfaceState]) { @@ -1793,21 +1797,42 @@ static BOOL ShouldUseNewRenderingRange = NO; } // Entered or exited contents rendering state. - if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); + BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); + + if (nowDisplay != wasDisplay) { if ([self supportsRangeManagedInterfaceState]) { - if (newState & ASInterfaceStateDisplay) { + if (nowDisplay) { // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. [self setDisplaySuspended:NO]; } else { [self setDisplaySuspended:YES]; [self clearContents]; } + } else { + // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all + // internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it. + if ([ASDisplayNode shouldUseNewRenderingRange] && !ASInterfaceStateIncludesVisible(newState)) { + // Check __implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. + if ([self __implementsDisplay]) { + if (nowDisplay) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; + } else { + [[self asyncLayer] cancelAsyncDisplay]; + [self clearContents]; + } + } + } } } - // Entered or exited data loading state. - if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { - if (newState & ASInterfaceStateVisible) { + // Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel + // is onscreen. If not range-managed, we can't guarantee more than the node being present in an onscreen window. + BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); + BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); + + if (nowVisible != wasVisible) { + if (nowVisible) { [self visibilityDidChange:YES]; } else { [self visibilityDidChange:NO]; @@ -1843,11 +1868,23 @@ static BOOL ShouldUseNewRenderingRange = NO; - (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState { + ASInterfaceState oldState = self.interfaceState; + ASInterfaceState newState = 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]; + + if ([self supportsRangeManagedInterfaceState]) { + // Instead of each node in the recursion assuming it needs to schedule itself for display, + // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). + // If our range manager intends for us to be displayed right now, and didn't before, get started! + + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); + BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); + if (nowDisplay && (nowDisplay != wasDisplay)) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; + } + } } - (ASHierarchyState)hierarchyState diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index e8e72c7391..0ab9957cc3 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -12,6 +12,23 @@ #import #import +// Because inline methods can't be extern'd and need to be part of the translation unit of code +// that compiles with them to actually inline, we both declare and define these in the header. +inline BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState) +{ + return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible); +} + +inline BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState) +{ + return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay); +} + +inline BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) +{ + return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData); +} + NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index bc98031e67..84eec7872e 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -17,21 +17,6 @@ #import "ASInternalHelpers.h" #import "ASDisplayNode+FrameworkPrivate.h" -extern BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState) -{ - return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible); -} - -extern BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState) -{ - return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay); -} - -extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState) -{ - return ((interfaceState & ASInterfaceStateFetchData) == ASInterfaceStateFetchData); -} - @interface ASRangeControllerBeta () { BOOL _rangeIsValid; diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 3152e4d7ce..24ab6b0d0e 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -110,6 +110,8 @@ ASDisplayNodeAssertMainThread(); ASDN::MutexLocker l(_displaySuspendedLock); + // FIXME: Reconsider whether we should cancel a display in progress. + // We should definitely cancel a display that is scheduled, but unstarted display. [self cancelAsyncDisplay]; // Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time. diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index dfe40084dc..abb428ebd1 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -39,7 +39,6 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; if (!(self = [super init])) { return nil; } - _layoutChildren = [NSMutableDictionary dictionary]; _isMutable = YES; return self; } @@ -56,11 +55,6 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; return self; } -- (void)setChild:(id)child; -{ - [self setChild:child forIdentifier:kDefaultChildKey]; -} - - (id)layoutableToAddFromLayoutable:(id)child { if (self.isFinalLayoutable == NO) { @@ -88,6 +82,19 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; return child; } +- (NSMutableDictionary *)layoutChildren +{ + if (!_layoutChildren) { + _layoutChildren = [NSMutableDictionary dictionary]; + } + return _layoutChildren; +} + +- (void)setChild:(id)child; +{ + [self setChild:child forIdentifier:kDefaultChildKey]; +} + - (void)setChild:(id)child forIdentifier:(NSString *)identifier { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 00fdf6bdde..7dd7275090 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -11,6 +11,7 @@ #import "ASInternalHelpers.h" #import "ASAssert.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNodeExtras.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" @@ -250,9 +251,11 @@ _messageToViewOrLayer(setNeedsDisplay); if ([ASDisplayNode shouldUseNewRenderingRange]) { - BOOL shouldDisplay = ((_interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay); - if (_layer && !_flags.synchronous && shouldDisplay) { - [ASDisplayNode scheduleNodeForDisplay:self]; + BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); + // FIXME: This should not need to recursively display, so create a non-recursive variant. + // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. + if (_layer && !_flags.synchronous && nowDisplay && [self __implementsDisplay]) { + [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } } } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index c1f752002d..42627d39c0 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -109,7 +109,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) #endif } -+ (void)scheduleNodeForDisplay:(ASDisplayNode *)node; ++ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node; // The _ASDisplayLayer backing the node, if any. @property (nonatomic, readonly, retain) _ASDisplayLayer *asyncLayer; From 90a1bb23469a38820adc681fca4aac23ea5c77ed Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 10 Jan 2016 02:58:48 -0800 Subject: [PATCH 39/39] Ruthlessly improve efficiency in ASRangeControllerBeta. - Use completedNodes directly, caching inner arrays and counts between loop iterations. - Merge codepaths between the "entire self - table / collection" visible or invisible cases - Ensure we do not trigger an assertion if a previous iteration's node is nil by the time we try to reset its interfaceState. --- .../Details/ASRangeControllerBeta.mm | 123 ++++++++++-------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm index 84eec7872e..e53000d9d2 100644 --- a/AsyncDisplayKit/Details/ASRangeControllerBeta.mm +++ b/AsyncDisplayKit/Details/ASRangeControllerBeta.mm @@ -80,36 +80,48 @@ [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; } + NSArray *allNodes = [_dataSource completedNodes]; + NSArray *currentSectionNodes = nil; + NSInteger currentSectionIndex = -1; // Will be unequal to any indexPath.section, so we set currentSectionNodes. + + NSUInteger numberOfSections = [allNodes count]; + NSUInteger numberOfNodesInSection = 0; + + NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths]; + // = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; + NSSet *displayIndexPaths = nil; + NSSet *fetchDataIndexPaths = nil; + NSMutableSet *allIndexPaths = nil; + NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil); + ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; - -#if RangeControllerLoggingEnabled - NSMutableArray *modified = [NSMutableArray array]; -#endif - if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { // If we are already visible, get busy! Better get started on preloading before the user scrolls more... - NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData]; - NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay]; + fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData]; + displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay]; // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. - NSMutableSet *allIndexPaths = [fetchDataIndexPaths mutableCopy]; + allIndexPaths = [fetchDataIndexPaths mutableCopy]; [allIndexPaths unionSet:displayIndexPaths]; [allIndexPaths unionSet:visibleIndexPaths]; + } else { + allIndexPaths = [visibleIndexPaths mutableCopy]; + } + + // Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any + // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic + // scroll or major main thread stall could cause entirely disjoint sets, but we must visit all. + NSSet *allCurrentIndexPaths = [allIndexPaths copy]; + [allIndexPaths unionSet:_allPreviousIndexPaths]; + _allPreviousIndexPaths = allCurrentIndexPaths; + + for (NSIndexPath *indexPath in allIndexPaths) { + // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. + // For consistency, make sure each node knows that it should measure itself if something changes. + ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; - // Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any - // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic - // scroll or major main thread stall could cause entirely disjoint sets, but we must visit all. - NSSet *allCurrentIndexPaths = [allIndexPaths copy]; - [allIndexPaths unionSet:_allPreviousIndexPaths]; - _allPreviousIndexPaths = allCurrentIndexPaths; - - for (NSIndexPath *indexPath in allIndexPaths) { - // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. - // For consistency, make sure each node knows that it should measure itself if something changes. - ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; - + if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { if ([fetchDataIndexPaths containsObject:indexPath]) { interfaceState |= ASInterfaceStateFetchData; } @@ -119,39 +131,49 @@ if ([visibleIndexPaths containsObject:indexPath]) { interfaceState |= ASInterfaceStateVisible; } - - ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { -#if RangeControllerLoggingEnabled - [modified addObject:indexPath]; -#endif - [node recursivelySetInterfaceState:interfaceState]; - } - } - } else { - // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the - // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. - // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - - for (NSIndexPath *indexPath in visibleIndexPaths) { + } else { + // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the + // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet. + // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. + // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport", // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above. - ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay; + if ([allCurrentIndexPaths containsObject:indexPath]) { + // We might be looking at an indexPath that was previously in-range, but now we need to clear it. + // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths. + interfaceState |= ASInterfaceStateDisplay; + interfaceState |= ASInterfaceStateFetchData; + } + } + + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + if (section >= 0 && row >= 0 && section < numberOfSections) { + if (section != currentSectionIndex) { + // Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce + // between the same ones. Still, this saves dozens of method calls to access the inner array and count. + currentSectionNodes = [allNodes objectAtIndex:section]; + numberOfNodesInSection = [currentSectionNodes count]; + currentSectionIndex = section; + } - ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { -#if RangeControllerLoggingEnabled - [modified addObject:indexPath]; -#endif - [node recursivelySetInterfaceState:interfaceState]; + if (row < numberOfNodesInSection) { + ASDisplayNode *node = [currentSectionNodes objectAtIndex:row]; + + ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); + // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. + if (node.interfaceState != interfaceState) { + [modifiedIndexPaths addObject:indexPath]; + [node recursivelySetInterfaceState:interfaceState]; + } } } } + _rangeIsValid = YES; + _queuedRangeUpdate = NO; + #if RangeControllerLoggingEnabled NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; @@ -161,9 +183,9 @@ NSLog(@"custom: %@", visibleNodePathsSet); } - [modified sortUsingSelector:@selector(compare:)]; + [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - for (NSIndexPath *indexPath in modified) { + for (NSIndexPath *indexPath in modifiedIndexPaths) { ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath]; ASInterfaceState interfaceState = node.interfaceState; BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); @@ -172,9 +194,6 @@ NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData); } #endif - - _rangeIsValid = YES; - _queuedRangeUpdate = NO; } #pragma mark - Cell node view handling