diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 6d853721ca..3af5c45254 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -366,7 +366,7 @@ /** - * Called just before the view is added to a superview. + * Called just before the view is added to a window. */ - (void)willEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER; @@ -426,9 +426,13 @@ @end @interface ASDisplayNode (ASDisplayNodePrivate) -// 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. -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass; +/** + * 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. + * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. + * If YES, this method must be called on the main thread and the node must not be layer-backed. + */ +- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; // The two methods below will eventually be exposed, but their names are subject to change. /** diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 80ee51aa6f..b10db67a5c 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -41,18 +41,32 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); typedef NS_OPTIONS(NSUInteger, ASInterfaceState) { /** The element is not predicted to be onscreen soon and preloading should not be performed */ - ASInterfaceStateNone = 1 << 0, + ASInterfaceStateNone = 0, /** The element may be added to a view soon that could become visible. Measure the layout, including size calculation. */ - ASInterfaceStateMeasureLayout = 1 << 1, + ASInterfaceStateMeasureLayout = 1 << 0, /** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */ - ASInterfaceStateFetchData = 1 << 2, + ASInterfaceStateFetchData = 1 << 1, /** The element is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ - ASInterfaceStateDisplay = 1 << 3, + ASInterfaceStateDisplay = 1 << 2, /** The element is physically onscreen by at least 1 pixel. In practice, all other bit fields should also be set when this flag is set. */ - ASInterfaceStateVisible = 1 << 4, + ASInterfaceStateVisible = 1 << 3, }; +/** + * Currently we only set `interfaceState` for + * nodes contained in table views or collection views. + + * Nodes that aren't contained in cells will be in this state when + * they are in the view hierarchy, and `ASInterfaceStateNone` when + * they aren't. + */ +static const 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/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 9c52f08c21..cc88b754ba 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -24,6 +24,7 @@ #import "ASInternalHelpers.h" #import "ASLayout.h" #import "ASLayoutSpec.h" +#import "ASCellNode.h" @interface ASDisplayNode () @@ -1581,6 +1582,10 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + + if (![self supportsInterfaceState]) { + self.interfaceState = ASInterfaceStateInHierarchy; + } } - (void)didExitHierarchy @@ -1588,6 +1593,10 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + + if (![self supportsInterfaceState]) { + self.interfaceState = ASInterfaceStateNone; + } } - (void)clearContents @@ -1635,6 +1644,20 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) [self clearFetchedData]; } +/** + * We currently only set interface state on nodes + * in table/collection views. For other nodes, if they are + * in the hierarchy we return `Unknown`, otherwise we return `None`. + * + * TODO: Avoid traversing up node hierarchy due to possible deadlock. + * @see https://github.com/facebook/AsyncDisplayKit/issues/900 + * Possible solution is to push `isInCellNode` state downward on `addSubnode`/`removeFromSupernode`. + */ +- (BOOL)supportsInterfaceState { + return ([self isKindOfClass:ASCellNode.class] + || [self _supernodeWithClass:ASCellNode.class checkViewHierarchy:NO] != nil); +} + - (ASInterfaceState)interfaceState { ASDN::MutexLocker l(_propertyLock); @@ -1871,7 +1894,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) // This method has proved helpful in a few rare scenarios, similar to a category extension on UIView, but assumes knowledge of _ASDisplayView. // It's considered private API for now and its use should not be encouraged. -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass +- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy { ASDisplayNode *supernode = self.supernode; while (supernode) { @@ -1879,6 +1902,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) return supernode; supernode = supernode.supernode; } + if (!checkViewHierarchy) { + return nil; + } UIView *view = self.view.superview; while (view) {