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..e53000d9d2 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;
@@ -95,36 +80,48 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
[_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;
}
@@ -134,39 +131,49 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
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];
@@ -176,9 +183,9 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
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);
@@ -187,9 +194,6 @@ extern BOOL ASInterfaceStateIncludesFetchData(ASInterfaceState interfaceState)
NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData);
}
#endif
-
- _rangeIsValid = YES;
- _queuedRangeUpdate = NO;
}
#pragma mark - Cell node view handling
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;