Merge branch 'master' into update-objc

Conflicts:
	AsyncDisplayKit/ASDisplayNode+Subclasses.h
This commit is contained in:
Adlai Holler 2015-12-06 12:25:13 -08:00
commit b16a9e294e
10 changed files with 193 additions and 39 deletions

View File

@ -21,8 +21,9 @@ typedef NSUInteger ASCellNodeAnimation;
* The notification is done on main thread. * The notification is done on main thread.
* *
* @param node A node informing the delegate about the relayout. * @param node A node informing the delegate about the relayout.
* @param sizeChanged `YES` if the node's `calculatedSize` changed during the relayout, `NO` otherwise.
*/ */
- (void)nodeDidRelayout:(ASCellNode *)node; - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged;
@end @end
/** /**

View File

@ -53,11 +53,13 @@
- (void)setNeedsLayout - (void)setNeedsLayout
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDisplayNodeAssertThreadAffinity(self);
CGSize oldSize = self.calculatedSize;
[super setNeedsLayout]; [super setNeedsLayout];
if (_layoutDelegate != nil) { if (_layoutDelegate != nil) {
BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize);
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
[_layoutDelegate nodeDidRelayout:self]; [_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged];
}); });
} }
} }

View File

@ -156,6 +156,7 @@ static BOOL _isInterceptedSelector(SEL sel)
BOOL _asyncDelegateImplementsInsetSection; BOOL _asyncDelegateImplementsInsetSection;
BOOL _collectionViewLayoutImplementsInsetSection; BOOL _collectionViewLayoutImplementsInsetSection;
BOOL _asyncDataSourceImplementsConstrainedSizeForNode; BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
BOOL _queuedNodeSizeUpdate;
ASBatchContext *_batchContext; ASBatchContext *_batchContext;
@ -911,10 +912,26 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark - ASCellNodeDelegate #pragma mark - ASCellNodeDelegate
- (void)nodeDidRelayout:(ASCellNode *)node - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
// Cause UICollectionView to requery for the new height of this node
if (!sizeChanged || _queuedNodeSizeUpdate) {
return;
}
_queuedNodeSizeUpdate = YES;
[self performSelector:@selector(requeryNodeSizes)
withObject:nil
afterDelay:0
inModes:@[ NSRunLoopCommonModes ]];
}
// Cause UICollectionView to requery for the new size of all nodes
- (void)requeryNodeSizes
{
_queuedNodeSizeUpdate = NO;
[super performBatchUpdates:^{} completion:nil]; [super performBatchUpdates:^{} completion:nil];
} }

View File

@ -368,7 +368,7 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
* 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; - (void)willEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER;
@ -428,9 +428,13 @@ NS_ASSUME_NONNULL_BEGIN
@end @end
@interface ASDisplayNode (ASDisplayNodePrivate) @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. * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView,
- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass; * 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.
*/
- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy;
// The two methods below will eventually be exposed, but their names are subject to change. // The two methods below will eventually be exposed, but their names are subject to change.
/** /**

View File

@ -43,18 +43,28 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
typedef NS_OPTIONS(NSUInteger, ASInterfaceState) typedef NS_OPTIONS(NSUInteger, ASInterfaceState)
{ {
/** The element is not predicted to be onscreen soon and preloading should not be performed */ /** 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. */ /** 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. */ /** 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. */ /** 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. /** 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. */ In practice, all other bit fields should also be set when this flag is set. */
ASInterfaceStateVisible = 1 << 4, ASInterfaceStateVisible = 1 << 3,
/**
* The node is not contained in a cell but it is in a window.
*
* Currently we only set `interfaceState` to other values for
* nodes contained in table views or collection views.
*/
ASInterfaceStateInHierarchy = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay | ASInterfaceStateVisible,
}; };
/** /**
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view * 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. * hierarchy off the main thread, and could do rendering off the main thread as well.

View File

@ -24,6 +24,7 @@
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASLayout.h" #import "ASLayout.h"
#import "ASLayoutSpec.h" #import "ASLayoutSpec.h"
#import "ASCellNode.h"
@interface ASDisplayNode () <UIGestureRecognizerDelegate> @interface ASDisplayNode () <UIGestureRecognizerDelegate>
@ -1581,6 +1582,10 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); 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"); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
if (![self supportsInterfaceState]) {
self.interfaceState = ASInterfaceStateInHierarchy;
}
} }
- (void)didExitHierarchy - (void)didExitHierarchy
@ -1588,6 +1593,10 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); 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"); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
if (![self supportsInterfaceState]) {
self.interfaceState = ASInterfaceStateNone;
}
} }
- (void)clearContents - (void)clearContents
@ -1635,6 +1644,20 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
[self clearFetchedData]; [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 - (ASInterfaceState)interfaceState
{ {
ASDN::MutexLocker l(_propertyLock); ASDN::MutexLocker l(_propertyLock);
@ -1643,14 +1666,20 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
- (void)setInterfaceState:(ASInterfaceState)interfaceState - (void)setInterfaceState:(ASInterfaceState)interfaceState
{ {
ASDN::MutexLocker l(_propertyLock); ASInterfaceState oldValue;
if (interfaceState != _interfaceState) { {
if ((interfaceState & ASInterfaceStateMeasureLayout) != (_interfaceState & ASInterfaceStateMeasureLayout)) { ASDN::MutexLocker l(_propertyLock);
oldValue = _interfaceState;
_interfaceState = interfaceState;
}
if (interfaceState != oldValue) {
if ((interfaceState & ASInterfaceStateMeasureLayout) != (oldValue & ASInterfaceStateMeasureLayout)) {
// Trigger asynchronous measurement if it is not already cached or being calculated. // Trigger asynchronous measurement if it is not already cached or being calculated.
} }
// Entered or exited data loading state. // Entered or exited data loading state.
if ((interfaceState & ASInterfaceStateFetchData) != (_interfaceState & ASInterfaceStateFetchData)) { if ((interfaceState & ASInterfaceStateFetchData) != (oldValue & ASInterfaceStateFetchData)) {
if (interfaceState & ASInterfaceStateFetchData) { if (interfaceState & ASInterfaceStateFetchData) {
[self fetchData]; [self fetchData];
} else { } else {
@ -1659,7 +1688,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
} }
// Entered or exited contents rendering state. // Entered or exited contents rendering state.
if ((interfaceState & ASInterfaceStateDisplay) != (_interfaceState & ASInterfaceStateDisplay)) { if ((interfaceState & ASInterfaceStateDisplay) != (oldValue & ASInterfaceStateDisplay)) {
if (interfaceState & ASInterfaceStateDisplay) { if (interfaceState & ASInterfaceStateDisplay) {
// Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here.
[self setDisplaySuspended:NO]; [self setDisplaySuspended:NO];
@ -1670,15 +1699,14 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
} }
// Entered or exited data loading state. // Entered or exited data loading state.
if ((interfaceState & ASInterfaceStateVisible) != (_interfaceState & ASInterfaceStateVisible)) { if ((interfaceState & ASInterfaceStateVisible) != (oldValue & ASInterfaceStateVisible)) {
if (interfaceState & ASInterfaceStateVisible) { if (interfaceState & ASInterfaceStateVisible) {
// Consider providing a -didBecomeVisible. // Consider providing a -didBecomeVisible.
} else { } else {
// Consider providing a -didBecomeInvisible. // Consider providing a -didBecomeInvisible.
} }
} }
_interfaceState = interfaceState;
} }
} }
@ -1871,7 +1899,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. // 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. // 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; ASDisplayNode *supernode = self.supernode;
while (supernode) { while (supernode) {
@ -1879,6 +1907,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
return supernode; return supernode;
supernode = supernode.supernode; supernode = supernode.supernode;
} }
if (!checkViewHierarchy) {
return nil;
}
UIView *view = self.view.superview; UIView *view = self.view.superview;
while (view) { while (view) {

View File

@ -181,6 +181,7 @@ static BOOL _isInterceptedSelector(SEL sel)
CGFloat _nodesConstrainedWidth; CGFloat _nodesConstrainedWidth;
BOOL _ignoreNodesConstrainedWidthChange; BOOL _ignoreNodesConstrainedWidthChange;
BOOL _queuedNodeHeightUpdate;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked; @property (atomic, assign) BOOL asyncDataSourceLocked;
@ -908,10 +909,26 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark - ASCellNodeLayoutDelegate #pragma mark - ASCellNodeLayoutDelegate
- (void)nodeDidRelayout:(ASCellNode *)node - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
// Cause UITableView to requery for the new height of this node
if (!sizeChanged || _queuedNodeHeightUpdate) {
return;
}
_queuedNodeHeightUpdate = YES;
[self performSelector:@selector(requeryNodeHeights)
withObject:nil
afterDelay:0
inModes:@[ NSRunLoopCommonModes ]];
}
// Cause UITableView to requery for the new height of this node
- (void)requeryNodeHeights
{
_queuedNodeHeightUpdate = NO;
[super beginUpdates]; [super beginUpdates];
[super endUpdates]; [super endUpdates];
} }

View File

@ -247,7 +247,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
@param attribute The attribute that was tapped. Will not be nil. @param attribute The attribute that was tapped. Will not be nil.
@param value The value of the tapped attribute. @param value The value of the tapped attribute.
@param point The point within textNode, in textNode's coordinate system, that was touched to trigger a highlight. @param point The point within textNode, in textNode's coordinate system, that was touched to trigger a highlight.
@discussion If not implemented, the default value is NO. @discussion If not implemented, the default value is YES.
@return YES if the entity attribute should be a link, NO otherwise. @return YES if the entity attribute should be a link, NO otherwise.
*/ */
- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point;

View File

@ -394,13 +394,15 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
return [self _linkAttributeValueAtPoint:point return [self _linkAttributeValueAtPoint:point
attributeName:attributeNameOut attributeName:attributeNameOut
range:rangeOut range:rangeOut
inAdditionalTruncationMessage:NULL]; inAdditionalTruncationMessage:NULL
forHighlighting:NO];
} }
- (id)_linkAttributeValueAtPoint:(CGPoint)point - (id)_linkAttributeValueAtPoint:(CGPoint)point
attributeName:(out NSString **)attributeNameOut attributeName:(out NSString **)attributeNameOut
range:(out NSRange *)rangeOut range:(out NSRange *)rangeOut
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
forHighlighting:(BOOL)highlighting
{ {
ASTextKitRenderer *renderer = [self _renderer]; ASTextKitRenderer *renderer = [self _renderer];
NSRange visibleRange = renderer.visibleRanges[0]; NSRange visibleRange = renderer.visibleRanges[0];
@ -453,10 +455,10 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
continue; continue;
} }
// Check if delegate implements optional method, if not assume NO. // If highlighting, check with delegate first. If not implemented, assume YES.
// Should the text be highlightable/touchable? if (highlighting
if (![_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)] || && [_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)]
![_delegate textNode:self shouldHighlightLinkAttribute:name value:value atPoint:point]) { && ![_delegate textNode:self shouldHighlightLinkAttribute:name value:value atPoint:point]) {
value = nil; value = nil;
name = nil; name = nil;
} }
@ -758,7 +760,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
id linkAttributeValue = [self _linkAttributeValueAtPoint:point id linkAttributeValue = [self _linkAttributeValueAtPoint:point
attributeName:&linkAttributeName attributeName:&linkAttributeName
range:&range range:&range
inAdditionalTruncationMessage:&inAdditionalTruncationMessage]; inAdditionalTruncationMessage:&inAdditionalTruncationMessage
forHighlighting:YES];
NSUInteger lastCharIndex = NSIntegerMax; NSUInteger lastCharIndex = NSIntegerMax;
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);
@ -778,11 +781,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
UITouch *touch = [touches anyObject]; CGPoint point = [[touches anyObject] locationInView:self.view];
UIView *view = touch.view;
CGPoint point = [touch locationInView:view];
point = [self.view convertPoint:point fromView:view];
NSRange range = NSMakeRange(0, 0); NSRange range = NSMakeRange(0, 0);
NSString *linkAttributeName = nil; NSString *linkAttributeName = nil;
@ -791,7 +790,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
id linkAttributeValue = [self _linkAttributeValueAtPoint:point id linkAttributeValue = [self _linkAttributeValueAtPoint:point
attributeName:&linkAttributeName attributeName:&linkAttributeName
range:&range range:&range
inAdditionalTruncationMessage:&inAdditionalTruncationMessage]; inAdditionalTruncationMessage:&inAdditionalTruncationMessage
forHighlighting:YES];
NSUInteger lastCharIndex = NSIntegerMax; NSUInteger lastCharIndex = NSIntegerMax;
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);
@ -835,7 +835,20 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
{ {
[super touchesMoved:touches withEvent:event]; [super touchesMoved:touches withEvent:event];
[self _clearHighlightIfNecessary]; // If touch has moved out of the current highlight range, clear the highlight.
if (_highlightRange.length > 0) {
NSRange range = NSMakeRange(0, 0);
CGPoint point = [[touches anyObject] locationInView:self.view];
[self _linkAttributeValueAtPoint:point
attributeName:NULL
range:&range
inAdditionalTruncationMessage:NULL
forHighlighting:YES];
if (!NSEqualRanges(_highlightRange, range)) {
[self _clearHighlightIfNecessary];
}
}
} }
- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer - (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer

View File

@ -15,6 +15,7 @@
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeTestsHelper.h" #import "ASDisplayNodeTestsHelper.h"
#import "UIView+ASConvenience.h" #import "UIView+ASConvenience.h"
#import "ASCellNode.h"
// Conveniences for making nodes named a certain way // Conveniences for making nodes named a certain way
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.name = @#n #define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.name = @#n
@ -76,11 +77,15 @@ for (ASDisplayNode *n in @[ nodes ]) {\
+ (dispatch_queue_t)asyncSizingQueue; + (dispatch_queue_t)asyncSizingQueue;
- (id)initWithViewClass:(Class)viewClass; - (id)initWithViewClass:(Class)viewClass;
- (id)initWithLayerClass:(Class)layerClass; - (id)initWithLayerClass:(Class)layerClass;
// FIXME: Importing ASDisplayNodeInternal.h causes a heap of problems.
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
@end @end
@interface ASTestDisplayNode : ASDisplayNode @interface ASTestDisplayNode : ASDisplayNode
@property (atomic, copy) void (^willDeallocBlock)(ASTestDisplayNode *node); @property (atomic, copy) void (^willDeallocBlock)(ASTestDisplayNode *node);
@property (atomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size); @property (atomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size);
@property (atomic) BOOL hasFetchedData;
@end @end
@interface ASTestResponderNode : ASTestDisplayNode @interface ASTestResponderNode : ASTestDisplayNode
@ -93,6 +98,18 @@ for (ASDisplayNode *n in @[ nodes ]) {\
return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero; return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero;
} }
- (void)fetchData
{
[super fetchData];
self.hasFetchedData = YES;
}
- (void)clearFetchedData
{
[super clearFetchedData];
self.hasFetchedData = NO;
}
- (void)dealloc - (void)dealloc
{ {
if (_willDeallocBlock) { if (_willDeallocBlock) {
@ -1666,6 +1683,48 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
[self checkBackgroundColorOpaqueRelationshipWithViewLoaded:NO layerBacked:YES]; [self checkBackgroundColorOpaqueRelationshipWithViewLoaded:NO layerBacked:YES];
} }
// Check that nodes who have no cell node (no range controller)
// do get their `fetchData` called, and they do report
// the fetch data interface state.
- (void)testInterfaceStateForNonCellNode
{
ASTestWindow *window = [ASTestWindow new];
ASTestDisplayNode *node = [ASTestDisplayNode new];
XCTAssert(node.interfaceState == ASInterfaceStateNone);
XCTAssert(!node.hasFetchedData);
[window addSubview:node.view];
XCTAssert(node.hasFetchedData);
XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy);
[node.view removeFromSuperview];
XCTAssert(!node.hasFetchedData);
XCTAssert(node.interfaceState == ASInterfaceStateNone);
}
// Check that nodes who have no cell node (no range controller)
// do get their `fetchData` called, and they do report
// the fetch data interface state.
- (void)testInterfaceStateForCellNode
{
ASCellNode *cellNode = [ASCellNode new];
ASTestDisplayNode *node = [ASTestDisplayNode new];
XCTAssert(node.interfaceState == ASInterfaceStateNone);
XCTAssert(!node.hasFetchedData);
// Simulate range handler updating cell node.
[cellNode addSubnode:node];
[cellNode enterInterfaceState:ASInterfaceStateFetchData];
XCTAssert(node.hasFetchedData);
XCTAssert(node.interfaceState == ASInterfaceStateFetchData);
// If the node goes into a view it should not adopt the `InHierarchy` state.
ASTestWindow *window = [ASTestWindow new];
[window addSubview:cellNode.view];
XCTAssert(node.hasFetchedData);
XCTAssert(node.interfaceState == ASInterfaceStateFetchData);
}
- (void)testInitWithViewClass - (void)testInitWithViewClass
{ {
ASDisplayNode *scrollNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]]; ASDisplayNode *scrollNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];