Merge commit '192e9398e53685be52e0eab26501a1f2a2926cdd'

This commit is contained in:
Peter
2016-03-16 14:57:35 +03:00
36 changed files with 913 additions and 377 deletions

View File

@@ -51,7 +51,7 @@ Pod::Spec.new do |spec|
spec.subspec 'PINRemoteImage' do |pin|
pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' }
pin.dependency 'PINRemoteImage/iOS', '>= 2'
pin.dependency 'PINRemoteImage/iOS', '>= 2.1'
pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject'
end

View File

@@ -498,6 +498,8 @@
DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; };
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 */; };
DE4843DB1C93EAB100A1F33B /* ASDisplayNodeLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */; };
DE4843DC1C93EAC100A1F33B /* ASDisplayNodeLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */; };
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 */; };
DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -514,6 +516,8 @@
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 */; };
E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */; };
E52405B51C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */; };
E5711A2B1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; };
E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.m in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */; };
@@ -845,6 +849,8 @@
DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASCollectionInternal.m; path = Details/ASCollectionInternal.m; sourceTree = "<group>"; };
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayoutContext.mm; sourceTree = "<group>"; };
E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeLayoutContext.h; sourceTree = "<group>"; };
E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = "<group>"; };
E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexedNodeContext.m; sourceTree = "<group>"; };
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1192,6 +1198,8 @@
058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */,
058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */,
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */,
E52405B41C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h */,
E52405B21C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm */,
058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */,
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
@@ -1453,6 +1461,7 @@
055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */,
ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */,
055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */,
E52405B51C8FEF16004DC8E7 /* ASDisplayNodeLayoutContext.h in Headers */,
ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */,
AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */,
291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */,
@@ -1605,6 +1614,7 @@
E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */,
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */,
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
DE4843DC1C93EAC100A1F33B /* ASDisplayNodeLayoutContext.h in Headers */,
254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */,
34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */,
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */,
@@ -1675,6 +1685,7 @@
058D09B9195D04C000B7D73C /* Frameworks */,
058D09BA195D04C000B7D73C /* Resources */,
3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */,
B130AB1AC0A1E5162E211C19 /* Embed Pods Frameworks */,
);
buildRules = (
);
@@ -1804,6 +1815,21 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
B130AB1AC0A1E5162E211C19 /* 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;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -1885,6 +1911,7 @@
ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */,
0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */,
257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */,
E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */,
257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */,
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
@@ -1965,6 +1992,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DE4843DB1C93EAB100A1F33B /* ASDisplayNodeLayoutContext.mm in Sources */,
B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */,
92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */,
636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */,

View File

@@ -32,4 +32,9 @@
*/
@property (nonatomic, weak) id<ASCellNodeLayoutDelegate> layoutDelegate;
/*
* Back-pointer to the containing scrollView instance, set only for visible cells. Used for Cell Visibility Event callbacks.
*/
@property (nonatomic, weak) UIScrollView *scrollView;
@end

View File

@@ -127,20 +127,38 @@
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
}
- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
CGSize oldSize = self.calculatedSize;
ASLayout *layout = [super transitionLayoutWithAnimation:animated];
[self didRelayoutFromOldSize:oldSize toNewSize:layout.size];
return layout;
[super transitionLayoutWithAnimation:animated
shouldMeasureAsync:shouldMeasureAsync
measurementCompletion:^{
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
if (completion) {
completion();
}
}
];
}
- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
CGSize oldSize = self.calculatedSize;
ASLayout *layout = [super transitionLayoutWithSizeRange:constrainedSize animated:animated];
[self didRelayoutFromOldSize:oldSize toNewSize:layout.size];
return layout;
[super transitionLayoutWithSizeRange:constrainedSize
animated:animated
shouldMeasureAsync:shouldMeasureAsync
measurementCompletion:^{
[self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize];
if (completion) {
completion();
}
}
];
}
- (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize
@@ -153,6 +171,9 @@
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
@@ -181,11 +202,25 @@
[(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event];
}
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event
inScrollView:(UIScrollView *)scrollView
withCellFrame:(CGRect)cellFrame
#pragma clang diagnostic pop
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame
{
// To be overriden by subclasses
// To be overriden by subclasses
}
- (void)visibilityDidChange:(BOOL)isVisible
{
[super visibilityDidChange:isVisible];
CGRect cellFrame = CGRectZero;
if (_scrollView) {
// It is not safe to message nil with a structure return value, so ensure our _scrollView has not died.
cellFrame = [self.view convertRect:self.bounds toView:_scrollView];
}
[self cellNodeVisibilityEvent:isVisible ? ASCellNodeVisibilityEventVisible : ASCellNodeVisibilityEventInvisible
inScrollView:_scrollView
withCellFrame:cellFrame];
}
@end

View File

@@ -15,6 +15,7 @@
#import "ASCollectionViewLayoutController.h"
#import "ASCollectionViewFlowLayoutInspector.h"
#import "ASCollectionViewLayoutFacilitatorProtocol.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
#import "ASInternalHelpers.h"
@@ -57,6 +58,36 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
@end
#pragma mark -
#pragma mark _ASCollectionViewNodeSizeUpdateContext
/**
* This class contains all the nodes that have a new size and UICollectionView should requery them all at once.
* It is intended to be used strictly on main thread and is not thread safe.
*/
@interface _ASCollectionViewNodeSizeInvalidationContext : NSObject
/**
* It's possible that a node triggered multiple size changes before main thread has a chance to execute `requeryNodeSizes`.
* Therefore, a set is preferred here, to avoid asking ASDataController to search for index path of the same node multiple times.
*/
@property (nonatomic, strong) NSMutableSet<ASCellNode *> *invalidatedNodes;
@property (nonatomic, assign) BOOL shouldAnimate;
@end
@implementation _ASCollectionViewNodeSizeInvalidationContext
- (instancetype)init
{
self = [super init];
if (self) {
_invalidatedNodes = [NSMutableSet set];
_shouldAnimate = YES;
}
return self;
}
@end
#pragma mark -
#pragma mark ASCollectionView.
@@ -78,7 +109,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
BOOL _asyncDelegateImplementsScrollviewDidScroll;
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath;
BOOL _queuedNodeSizeUpdate;
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
BOOL _isDeallocating;
ASBatchContext *_batchContext;
@@ -532,39 +563,35 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
ASCellNode *cellNode = [cell node];
cellNode.scrollView = collectionView;
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
}
ASCellNode *cellNode = [cell node];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if (cellNode.neverShowPlaceholders) {
[cellNode recursivelyEnsureDisplaySynchronously:YES];
}
if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) {
[_cellsForVisibilityUpdates addObject:cell];
[cellNode cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisible
inScrollView:collectionView
withCellFrame:cell.frame];
}
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
ASCellNode *cellNode = [cell node];
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]) {
ASCellNode *node = ((_ASCollectionViewCell *)cell).node;
ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate collectionView:self didEndDisplayingNode:node forItemAtIndexPath:indexPath];
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath];
}
if ([_cellsForVisibilityUpdates containsObject:cell]) {
ASCellNode *node = ((_ASCollectionViewCell *)cell).node;
[node cellNodeVisibilityEvent:ASCellNodeVisibilityEventInvisible
inScrollView:collectionView
withCellFrame:cell.frame];
[_cellsForVisibilityUpdates removeObject:cell];
}
@@ -574,6 +601,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
}
#pragma clang diagnostic pop
cellNode.scrollView = nil;
}
#pragma mark -
@@ -867,7 +896,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController
{
ASDisplayNodeAssertMainThread();
return [self indexPathsForVisibleItems];
// Calling visibleNodeIndexPathsForRangeController: will trigger UIKit to call reloadData if it never has, which can result
// in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast.
BOOL isZeroSized = CGRectEqualToRect(self.bounds, CGRectZero);
return isZeroSized ? @[] : [self indexPathsForVisibleItems];
}
- (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController
@@ -1017,23 +1049,61 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{
ASDisplayNodeAssertMainThread();
if (!sizeChanged || _queuedNodeSizeUpdate) {
if (!sizeChanged) {
return;
}
_queuedNodeSizeUpdate = YES;
[self performSelector:@selector(requeryNodeSizes)
withObject:nil
afterDelay:0
inModes:@[ NSRunLoopCommonModes ]];
BOOL queued = (_queuedNodeSizeInvalidationContext != nil);
if (!queued) {
_queuedNodeSizeInvalidationContext = [[_ASCollectionViewNodeSizeInvalidationContext alloc] init];
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf requeryNodeSizes];
}
});
}
[_queuedNodeSizeInvalidationContext.invalidatedNodes addObject:node];
// Check if this node or one of its subnodes can be animated.
// If the context is already non-animated, don't bother checking this node.
if (_queuedNodeSizeInvalidationContext.shouldAnimate) {
BOOL (^shouldNotAnimateBlock)(ASDisplayNode *) = ^BOOL(ASDisplayNode * _Nonnull node) {
return node.shouldAnimateSizeChanges == NO;
};
if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) {
// One single non-animated cell node causes the whole context to be non-animated
_queuedNodeSizeInvalidationContext.shouldAnimate = NO;
}
}
}
// Cause UICollectionView to requery for the new size of all nodes
- (void)requeryNodeSizes
{
_queuedNodeSizeUpdate = NO;
ASDisplayNodeAssertMainThread();
NSSet<ASCellNode *> *nodes = _queuedNodeSizeInvalidationContext.invalidatedNodes;
NSMutableArray<NSIndexPath *> *indexPaths = [NSMutableArray arrayWithCapacity:nodes.count];
for (ASCellNode *node in nodes) {
NSIndexPath *indexPath = [self indexPathForNode:node];
if (indexPath != nil) {
[indexPaths addObject:indexPath];
}
}
[super performBatchUpdates:^{} completion:nil];
if (indexPaths.count > 0) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
ASPerformBlockWithoutAnimation(!_queuedNodeSizeInvalidationContext.shouldAnimate, ^{
// Perform an empty update transaction here to trigger UICollectionView to requery row sizes and layout its subviews again
[super performBatchUpdates:^{} completion:nil];
});
}
_queuedNodeSizeInvalidationContext = nil;
}
#pragma mark - Memory Management

View File

@@ -120,11 +120,6 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) {
*/
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event;
/**
Class method to enable a visualization overlay of the tapable area on the ASControlNode. For app debugging purposes only.
@param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
*/
+ (void)setEnableHitTestDebug:(BOOL)enable;
@end
NS_ASSUME_NONNULL_END

View File

@@ -89,7 +89,8 @@ static BOOL _enableHitTestDebug = NO;
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
#pragma mark - ASDisplayNode Overrides
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
@@ -207,6 +208,8 @@ static BOOL _enableHitTestDebug = NO;
withEvent:event];
}
#pragma clang diagnostic pop
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir.

View File

@@ -57,18 +57,38 @@ ASDISPLAYNODE_EXTERN_C_END
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context;
/**
* @abstract Transitions the current layout with a new constrained size.
* @abstract Transitions the current layout with a new constrained size. Must be called on main thread.
*
* @discussion Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* If the passed constrainedSize is the the same as the node's current constrained size, this method is noop.
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
*
* @param shouldMeasureAsync Measure the layout asynchronously.
*
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
* It is called on main, right after the measurement and before -animateLayoutTransition:.
*
* @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop.
*
* @see animateLayoutTransition:
*/
- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated;
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion;
/**
* @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`.
* @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread.
*
* @discussion Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
*
* @param shouldMeasureAsync Measure the layout asynchronously.
*
* @param measurementCompletion Optional completion block called only if a new layout is calculated.
* It is called right after the measurement and before -animateLayoutTransition:.
*
* @see animateLayoutTransition:
*/
- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated;
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion;
@end

View File

@@ -206,6 +206,8 @@ NS_ASSUME_NONNULL_BEGIN
*
* @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) is
* about to begin.
*
* @note Called on the main thread only
*/
- (void)displayWillStart ASDISPLAYNODE_REQUIRES_SUPER;
@@ -214,6 +216,8 @@ NS_ASSUME_NONNULL_BEGIN
*
* @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) has
* completed.
*
* @note Called on the main thread only
*/
- (void)displayDidFinish ASDISPLAYNODE_REQUIRES_SUPER;
@@ -225,9 +229,14 @@ NS_ASSUME_NONNULL_BEGIN
* @discussion Subclasses may use this to monitor when they become visible, should free cached data, and much more.
* @see ASInterfaceState
*/
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState;
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER;
- (void)visibilityDidChange:(BOOL)isVisible;
/**
* @abstract Called whenever the visiblity of the node changed.
*
* @discussion Subclasses may use this to monitor when they become visible.
*/
- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER;
/**
* Called just before the view is added to a window.
@@ -340,7 +349,7 @@ NS_ASSUME_NONNULL_BEGIN
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Tells the node when touches moved in its view.
@@ -348,7 +357,7 @@ NS_ASSUME_NONNULL_BEGIN
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Tells the node when touches ended in its view.
@@ -356,7 +365,7 @@ NS_ASSUME_NONNULL_BEGIN
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Tells the node when touches was cancelled in its view.
@@ -364,7 +373,7 @@ NS_ASSUME_NONNULL_BEGIN
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/** @name Managing Gesture Recognizers */

View File

@@ -428,6 +428,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) BOOL displaySuspended;
/**
* @abstract Whether size changes should be animated. Default to YES.
*/
@property (nonatomic, assign) BOOL shouldAnimateSizeChanges;
/**
* @abstract Prevent the node and its descendants' layer from displaying.
*

View File

@@ -9,6 +9,7 @@
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
#import "ASLayoutOptionsPrivate.h"
#import <objc/runtime.h>
@@ -20,10 +21,10 @@
#import "_ASDisplayView.h"
#import "_ASScopeTimer.h"
#import "_ASCoreAnimationExtras.h"
#import "ASDisplayNodeLayoutContext.h"
#import "ASDisplayNodeExtras.h"
#import "ASEqualityHelpers.h"
#import "ASRunLoopQueue.h"
#import "NSArray+Diffing.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
@@ -34,7 +35,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes";
NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp";
@interface ASDisplayNode () <UIGestureRecognizerDelegate, _ASDisplayLayerDelegate, _ASTransitionContextDelegate>
@interface ASDisplayNode () <UIGestureRecognizerDelegate, _ASDisplayLayerDelegate, _ASTransitionContextCompletionDelegate>
/**
*
@@ -113,6 +114,7 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i
flags.isInHierarchy = NO;
flags.displaysAsynchronously = YES;
flags.shouldAnimateSizeChanges = YES;
flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
if (instance) {
@@ -219,7 +221,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0;
[dequeuedItem __recursivelyTriggerDisplayAndBlock:NO];
[dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
if (isQueueDrained) {
[[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil
@@ -350,9 +352,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
[self __setSupernode:nil];
_pendingViewState = nil;
_replaceAsyncSentinel = nil;
_displaySentinel = nil;
_transitionSentinel = nil;
_pendingDisplayNodes = nil;
}
@@ -583,111 +585,162 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
void (^manageSubnodesBlock)() = ^void() {
ASDN::MutexLocker l(_propertyLock);
if (self.usesImplicitHierarchyManagement) {
[self __implicitlyInsertSubnodes];
[self __implicitlyRemoveSubnodes];
}
[self __completeLayoutCalculation];
};
ASDN::MutexLocker l(_propertyLock);
if (! [self shouldMeasureWithSizeRange:constrainedSize]) {
return _layout;
}
if ([self _hasTransitionsInProgress]) {
// Invalidate transition sentinel to cancel transitions in progress
[self _invalidateTransitionSentinel];
// Tell subnodes to exit layout pending state and clear related properties
ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) {
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
});
}
ASLayout *previousLayout = _layout;
ASSizeRange previousConstrainedSize = _constrainedSize;
ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize];
return [self measureWithSizeRange:constrainedSize completion:^{
if (!self.isNodeLoaded) {
manageSubnodesBlock();
} else {
ASPerformBlockOnMainThread(manageSubnodesBlock);
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
_pendingLayoutContext = [[ASDisplayNodeLayoutContext alloc] initWithNode:self
pendingLayout:newLayout
pendingConstrainedSize:constrainedSize
previousLayout:previousLayout
previousConstrainedSize:previousConstrainedSize];
} else {
ASDisplayNodeLayoutContext *layoutContext;
if (self.usesImplicitHierarchyManagement) {
layoutContext = [[ASDisplayNodeLayoutContext alloc] initWithNode:self
pendingLayout:newLayout
pendingConstrainedSize:constrainedSize
previousLayout:previousLayout
previousConstrainedSize:previousConstrainedSize];
}
}];
[self applyLayout:newLayout constrainedSize:constrainedSize layoutContext:layoutContext];
[self _completeLayoutCalculation];
}
return newLayout;
}
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion
- (BOOL)shouldMeasureWithSizeRange:(ASSizeRange)constrainedSize
{
ASDN::MutexLocker l(_propertyLock);
if (![self __shouldSize])
return nil;
if (![self __shouldSize]) {
return NO;
}
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) && constrainedSize.transitionID != _pendingTransitionID) {
return NO;
}
// only calculate the size if
// - we haven't already
// - the constrained size range is different
if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) {
_previousLayout = _layout;
_layout = [self calculateLayoutThatFits:constrainedSize];
return (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize));
}
ASDisplayNodeAssertTrue(_layout.layoutableObject == self);
ASDisplayNodeAssertTrue(_layout.size.width >= 0.0);
ASDisplayNodeAssertTrue(_layout.size.height >= 0.0);
_previousConstrainedSize = _constrainedSize;
_constrainedSize = constrainedSize;
if (self.usesImplicitHierarchyManagement) {
[self __calculateSubnodeOperations];
}
_flags.isMeasured = YES;
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
ASSizeRange currentConstrainedSize = _constrainedSize;
[self invalidateCalculatedLayout];
[self transitionLayoutWithSizeRange:currentConstrainedSize
animated:animated
shouldMeasureAsync:shouldMeasureAsync
measurementCompletion:completion];
}
completion();
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(void(^)())completion
{
ASDisplayNodeAssertMainThread();
if (! [self shouldMeasureWithSizeRange:constrainedSize]) {
return;
}
{
ASDN::MutexLocker l(_propertyLock);
ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one.");
}
return _layout;
}
int32_t transitionID = [self _newTransitionID];
constrainedSize.transitionID = transitionID;
- (ASLayout *)transitionLayoutWithAnimation:(BOOL)animated
{
[self invalidateCalculatedLayout];
return [self transitionLayoutWithSizeRange:_constrainedSize animated:animated];
}
- (ASLayout *)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated
{
BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO;
self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x
ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) {
ASDisplayNodeAssert([node _hasTransitionsInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one.");
node.hierarchyState |= ASHierarchyStateLayoutPending;
node.pendingTransitionID = transitionID;
});
return [self measureWithSizeRange:constrainedSize completion:^{
if (disableImplicitHierarchyManagement) {
self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x
}
ASPerformBlockOnMainThread(^{
void (^transitionBlock)() = ^{
ASLayout *newLayout;
{
ASDN::MutexLocker l(_propertyLock);
_transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self];
[self __implicitlyInsertSubnodes];
BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO;
self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x
newLayout = [self calculateLayoutThatFits:constrainedSize];
if (disableImplicitHierarchyManagement) {
self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x
}
}
if ([self _shouldAbortTransitionWithID:transitionID]) {
return;
}
ASPerformBlockOnMainThread(^{
if ([self _shouldAbortTransitionWithID:transitionID]) {
return;
}
ASDN::MutexLocker l(_propertyLock);
ASLayout *previousLayout = _layout;
ASSizeRange previousConstrainedSize = _constrainedSize;
[self applyLayout:newLayout constrainedSize:constrainedSize layoutContext:nil];
[self _invalidateTransitionSentinel];
ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode * _Nonnull node) {
[node applyPendingLayoutContext];
[node _completeLayoutCalculation];
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
});
if (completion) {
completion();
}
_pendingLayoutContext = [[ASDisplayNodeLayoutContext alloc] initWithNode:self
pendingLayout:newLayout
pendingConstrainedSize:constrainedSize
previousLayout:previousLayout
previousConstrainedSize:previousConstrainedSize];
[_pendingLayoutContext applySubnodeInsertions];
_transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
layoutDelegate:_pendingLayoutContext
completionDelegate:self];
[self animateLayoutTransition:_transitionContext];
});
}];
}
};
- (void)__calculateSubnodeOperations
{
ASDN::MutexLocker l(_propertyLock);
if (_previousLayout) {
NSIndexSet *insertions, *deletions;
[_previousLayout.immediateSublayouts asdk_diffWithArray:_layout.immediateSublayouts
insertions:&insertions
deletions:&deletions
compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject);
}];
filterNodesInLayoutAtIndexes(_layout, insertions, &_insertedSubnodes, &_insertedSubnodePositions);
filterNodesInLayoutAtIndexesWithIntersectingNodes(_previousLayout,
deletions,
_insertedSubnodes,
&_removedSubnodes,
&_removedSubnodePositions);
if (shouldMeasureAsync) {
ASPerformBlockOnBackgroundThread(transitionBlock);
} else {
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_layout.immediateSublayouts count])];
filterNodesInLayoutAtIndexes(_layout, indexes, &_insertedSubnodes, &_insertedSubnodePositions);
_removedSubnodes = nil;
transitionBlock();
}
}
- (void)__completeLayoutCalculation
- (void)_completeLayoutCalculation
{
ASDN::MutexLocker l(_propertyLock);
_insertedSubnodes = nil;
_removedSubnodes = nil;
_previousLayout = nil;
[self calculatedLayoutDidChange];
// we generate placeholders at measureWithSizeRange: time so that a node is guaranteed
@@ -704,53 +757,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
}
}
/**
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
*/
static inline void filterNodesInLayoutAtIndexes(
ASLayout *layout,
NSIndexSet *indexes,
NSArray<ASDisplayNode *> * __strong *storedNodes,
std::vector<NSInteger> *storedPositions
)
{
filterNodesInLayoutAtIndexesWithIntersectingNodes(layout, indexes, nil, storedNodes, storedPositions);
}
/**
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
* @discussion If the node exists in the `intersectingNodes` array, the node is not added to `storedNodes`.
*/
static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes(
ASLayout *layout,
NSIndexSet *indexes,
NSArray<ASDisplayNode *> *intersectingNodes,
NSArray<ASDisplayNode *> * __strong *storedNodes,
std::vector<NSInteger> *storedPositions
)
{
NSMutableArray<ASDisplayNode *> *nodes = [NSMutableArray array];
std::vector<NSInteger> positions = std::vector<NSInteger>();
NSInteger idx = [indexes firstIndex];
while (idx != NSNotFound) {
BOOL skip = NO;
ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject;
ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts");
for (ASDisplayNode *i in intersectingNodes) {
if (node == i) {
skip = YES;
break;
}
}
if (!skip) {
[nodes addObject:node];
positions.push_back(idx);
}
idx = [indexes indexGreaterThanIndex:idx];
}
*storedNodes = nodes;
*storedPositions = positions;
}
- (void)calculatedLayoutDidChange
{
@@ -779,66 +785,12 @@ static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes(
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
{
[self __implicitlyRemoveSubnodes];
[self __completeLayoutCalculation];
[_pendingLayoutContext applySubnodeRemovals];
[self _completeLayoutCalculation];
_pendingLayoutContext = nil;
}
#pragma mark - Implicit node hierarchy managagment
- (void)__implicitlyInsertSubnodes
{
ASDN::MutexLocker l(_propertyLock);
for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) {
NSInteger p = _insertedSubnodePositions[i];
[self insertSubnode:_insertedSubnodes[i] atIndex:p];
}
}
- (void)__implicitlyRemoveSubnodes
{
ASDN::MutexLocker l(_propertyLock);
for (NSInteger i = 0; i < [_removedSubnodes count]; i++) {
[_removedSubnodes[i] removeFromSupernode];
}
}
#pragma mark - _ASTransitionContextDelegate
- (NSArray<ASDisplayNode *> *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context
{
return _subnodes;
}
- (NSArray<ASDisplayNode *> *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context
{
return _insertedSubnodes;
}
- (NSArray<ASDisplayNode *> *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context
{
return _removedSubnodes;
}
- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key
{
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
return _previousLayout;
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
return _layout;
} else {
return nil;
}
}
- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key
{
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
return _previousConstrainedSize;
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
return _constrainedSize;
} else {
return ASSizeRangeMake(CGSizeZero, CGSizeZero);
}
}
#pragma mark - _ASTransitionContextCompletionDelegate
- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete
{
@@ -1706,7 +1658,7 @@ static NSInteger incrementIfFound(NSInteger i) {
// The node sending the message should usually be passed as the parameter, similar to the delegation pattern.
- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node
{
ASDN::MutexLocker l(_propertyLock);
ASDisplayNodeAssertMainThread();
if (!_pendingDisplayNodes) {
_pendingDisplayNodes = [[NSMutableSet alloc] init];
@@ -1719,27 +1671,25 @@ static NSInteger incrementIfFound(NSInteger i) {
// The node sending the message should usually be passed as the parameter, similar to the delegation pattern.
- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node
{
ASDN::MutexLocker l(_propertyLock);
ASDisplayNodeAssertMainThread();
[_pendingDisplayNodes removeObject:node];
// only trampoline if there is a placeholder and nodes are done displaying
if ([self _pendingDisplayNodesHaveFinished] && _placeholderLayer.superlayer) {
dispatch_async(dispatch_get_main_queue(), ^{
void (^cleanupBlock)() = ^{
[self _tearDownPlaceholderLayer];
};
void (^cleanupBlock)() = ^{
[self _tearDownPlaceholderLayer];
};
if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) {
[CATransaction begin];
[CATransaction setCompletionBlock:cleanupBlock];
[CATransaction setAnimationDuration:_placeholderFadeDuration];
_placeholderLayer.opacity = 0.0;
[CATransaction commit];
} else {
cleanupBlock();
}
});
if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) {
[CATransaction begin];
[CATransaction setCompletionBlock:cleanupBlock];
[CATransaction setAnimationDuration:_placeholderFadeDuration];
_placeholderLayer.opacity = 0.0;
[CATransaction commit];
} else {
cleanupBlock();
}
}
}
@@ -1808,7 +1758,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}
}
- (void)__recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock
- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock
{
ASDisplayNodeAssertMainThread();
@@ -1824,7 +1774,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously
{
[self __recursivelyTriggerDisplayAndBlock:synchronously];
[self _recursivelyTriggerDisplayAndBlock:synchronously];
}
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
@@ -1896,6 +1846,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
return _constrainedSize;
}
- (void)setPendingTransitionID:(int32_t)pendingTransitionID
{
ASDN::MutexLocker l(_propertyLock);
ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID);
_pendingTransitionID = pendingTransitionID;
}
- (void)setPreferredFrameSize:(CGSize)preferredFrameSize
{
ASDN::MutexLocker l(_propertyLock);
@@ -2026,6 +1983,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)visibilityDidChange:(BOOL)isVisible
{
// subclass override
}
/**
@@ -2045,6 +2003,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)setInterfaceState:(ASInterfaceState)newState
{
// It should never be possible for a node to be visible but not be allowed / expected to display.
ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState));
ASInterfaceState oldState = ASInterfaceStateNone;
{
ASDN::MutexLocker l(_propertyLock);
@@ -2211,6 +2171,19 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}
}
if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) {
if (newState & ASHierarchyStateLayoutPending) {
// Entering layout pending state
} else {
// Leaving layout pending state, reset related properties
{
ASDN::MutexLocker l(_propertyLock);
_pendingTransitionID = 0;
_pendingLayoutContext = nil;
}
}
}
if (newState != oldState) {
LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState);
}
@@ -2236,6 +2209,37 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
});
}
- (void)applyPendingLayoutContext
{
ASDN::MutexLocker l(_propertyLock);
if (_pendingLayoutContext) {
[self applyLayout:_pendingLayoutContext.pendingLayout
constrainedSize:_pendingLayoutContext.pendingConstrainedSize
layoutContext:_pendingLayoutContext];
_pendingLayoutContext = nil;
}
}
- (void)applyLayout:(ASLayout *)layout
constrainedSize:(ASSizeRange)constrainedSize
layoutContext:(ASDisplayNodeLayoutContext *)layoutContext
{
ASDN::MutexLocker l(_propertyLock);
_layout = layout;
ASDisplayNodeAssertTrue(layout.layoutableObject == self);
ASDisplayNodeAssertTrue(layout.size.width >= 0.0);
ASDisplayNodeAssertTrue(layout.size.height >= 0.0);
_constrainedSize = constrainedSize;
_flags.isMeasured = YES;
if (self.usesImplicitHierarchyManagement && layoutContext != nil) {
[layoutContext applySubnodeInsertions];
[layoutContext applySubnodeRemovals];
}
}
- (void)layout
{
ASDisplayNodeAssertMainThread();
@@ -2256,6 +2260,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)displayWillStart
{
ASDisplayNodeAssertMainThread();
// in case current node takes longer to display than it's subnodes, treat it as a dependent node
[self _pendingNodeWillDisplay:self];
@@ -2284,6 +2290,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)displayDidFinish
{
ASDisplayNodeAssertMainThread();
[self _pendingNodeDidDisplay:self];
[_supernode subnodeDisplayDidFinish:self];
@@ -2490,14 +2498,31 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
self.asyncLayer.displaySuspended = flag;
if ([self __implementsDisplay]) {
if (flag) {
[_supernode subnodeDisplayDidFinish:self];
} else {
[_supernode subnodeDisplayWillStart:self];
}
// Display start and finish methods needs to happen on the main thread
ASPerformBlockOnMainThread(^{
if (flag) {
[_supernode subnodeDisplayDidFinish:self];
} else {
[_supernode subnodeDisplayWillStart:self];
}
});
}
}
- (BOOL)shouldAnimateSizeChanges
{
ASDisplayNodeAssertThreadAffinity(self);
ASDN::MutexLocker l(_propertyLock);
return _flags.shouldAnimateSizeChanges;
}
-(void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges
{
ASDisplayNodeAssertThreadAffinity(self);
ASDN::MutexLocker l(_propertyLock);
_flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges;
}
static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
- (void)setDrawingPriority:(NSInteger)drawingPriority
@@ -2552,24 +2577,31 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
return asyncSizingQueue;
}
- (BOOL)_isMarkedForReplacement
- (BOOL)_hasTransitionsInProgress
{
ASDN::MutexLocker l(_propertyLock);
return _replaceAsyncSentinel != nil;
return _transitionSentinel != nil;
}
// FIXME: This method doesn't appear to be called, and could be removed.
// However, it may be useful for an API similar to what Paper used to create a new node hierarchy,
// trigger asynchronous measurement and display on it, and have it swap out and replace an old hierarchy.
- (ASSentinel *)_asyncReplaceSentinel
- (void)_invalidateTransitionSentinel
{
ASDN::MutexLocker l(_propertyLock);
_transitionSentinel = nil;
}
if (!_replaceAsyncSentinel) {
_replaceAsyncSentinel = [[ASSentinel alloc] init];
- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID
{
ASDN::MutexLocker l(_propertyLock);
return _transitionSentinel == nil || transitionID != _transitionSentinel.value;
}
- (int32_t)_newTransitionID
{
ASDN::MutexLocker l(_propertyLock);
if (!_transitionSentinel) {
_transitionSentinel = [[ASSentinel alloc] init];
}
return _replaceAsyncSentinel;
return [_transitionSentinel increment];
}
// Calls completion with nil to indicated cancellation

View File

@@ -91,12 +91,12 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^b
/**
Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block.
*/
extern id _Nullable ASDisplayNodeFind(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node));
extern id _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node));
/**
Given a display node, traverses up the layer tree hierarchy, returning the first display node of kind class.
*/
extern id _Nullable ASDisplayNodeFindClass(ASDisplayNode *start, Class c);
extern id _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c);
/**
* Given two nodes, finds their most immediate common parent. Used for geometry conversion methods.
@@ -124,7 +124,12 @@ extern NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodes(ASDisplayNode *sta
extern NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c);
/**
Given a display node, traverses down the node hierarchy, returning the depth-first display node that pass the block.
Given a display node, traverses down the node hierarchy, returning the depth-first display node, including the start node that pass the block.
*/
extern __kindof ASDisplayNode * ASDisplayNodeFindFirstNode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node));
/**
Given a display node, traverses down the node hierarchy, returning the depth-first display node, excluding the start node, that pass the block
*/
extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node));

View File

@@ -53,7 +53,7 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^b
}
}
id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
id ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
{
CALayer *layer = node.layer;
@@ -68,9 +68,9 @@ id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
return nil;
}
id ASDisplayNodeFindClass(ASDisplayNode *start, Class c)
id ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c)
{
return ASDisplayNodeFind(start, ^(ASDisplayNode *n) {
return ASDisplayNodeFindFirstSupernode(start, ^(ASDisplayNode *n) {
return [n isKindOfClass:c];
});
}
@@ -128,10 +128,10 @@ extern NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNo
#pragma mark - Find first subnode
static ASDisplayNode *_ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node))
static ASDisplayNode *_ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node))
{
for (ASDisplayNode *subnode in startNode.subnodes) {
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstSubnode(subnode, YES, block);
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block);
if (foundNode) {
return foundNode;
}
@@ -143,9 +143,14 @@ static ASDisplayNode *_ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, B
return nil;
}
extern __kindof ASDisplayNode * ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
{
return _ASDisplayNodeFindFirstNode(startNode, YES, block);
}
extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
{
return _ASDisplayNodeFindFirstSubnode(startNode, NO, block);
return _ASDisplayNodeFindFirstNode(startNode, NO, block);
}
extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c)

View File

@@ -96,7 +96,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
* @discussion Can be used to add image effects (such as rounding, adding
* borders, or other pattern overlays) without extraneous display calls.
*/
@property (nonatomic, readwrite, copy) asimagenode_modification_block_t imageModificationBlock;
@property (nullable, nonatomic, readwrite, copy) asimagenode_modification_block_t imageModificationBlock;
/**
* @abstract Marks the receiver as needing display and performs a block after

View File

@@ -49,6 +49,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
BOOL _cacheSupportsNewProtocol;
BOOL _cacheSupportsClearing;
BOOL _cacheSupportsSynchronousFetch;
}
@end
@@ -73,6 +74,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)];
_cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
_cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)];
_shouldCacheImage = YES;
self.shouldBypassEnsureDisplay = YES;
@@ -169,6 +171,17 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
- (void)displayWillStart
{
[super displayWillStart];
if (_cacheSupportsSynchronousFetch) {
ASDN::MutexLocker l(_lock);
if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) {
UIImage *result = [_cache synchronouslyFetchedCachedImageWithURL:_URL];
if (result) {
self.image = result;
_imageLoaded = YES;
}
}
}
[self fetchData];
@@ -184,6 +197,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
in ASMultiplexImageNode as well. */
- (void)visibilityDidChange:(BOOL)isVisible
{
[super visibilityDidChange:isVisible];
if (_downloaderImplementsSetPriority) {
ASDN::MutexLocker l(_lock);
if (_downloadIdentifier != nil) {

View File

@@ -615,24 +615,23 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
_pendingVisibleIndexPath = indexPath;
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
ASCellNode *cellNode = [cell node];
cellNode.scrollView = tableView;
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) {
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
}
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
ASCellNode *cellNode = [cell node];
if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) {
[_cellsForVisibilityUpdates addObject:cell];
[cellNode cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisible
inScrollView:tableView
withCellFrame:cell.frame];
}
if (cellNode.neverShowPlaceholders) {
[cellNode recursivelyEnsureDisplaySynchronously:YES];
}
if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) {
[_cellsForVisibilityUpdates addObject:cell];
}
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
@@ -640,21 +639,18 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
if ([_pendingVisibleIndexPath isEqual:indexPath]) {
_pendingVisibleIndexPath = nil;
}
ASCellNode *cellNode = [cell node];
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) {
ASCellNode *node = ((_ASTableViewCell *)cell).node;
ASDisplayNodeAssertNotNil(node, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate tableView:self didEndDisplayingNode:node forRowAtIndexPath:indexPath];
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
[_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath];
}
if ([_cellsForVisibilityUpdates containsObject:cell]) {
[_cellsForVisibilityUpdates removeObject:cell];
ASCellNode *node = ((_ASTableViewCell *)cell).node;
[node cellNodeVisibilityEvent:ASCellNodeVisibilityEventInvisible
inScrollView:tableView
withCellFrame:cell.frame];
}
#pragma clang diagnostic push
@@ -663,6 +659,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath];
}
#pragma clang diagnostic pop
cellNode.scrollView = nil;
}
@@ -718,6 +716,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
{
ASDisplayNodeAssertMainThread();
// Calling indexPathsForVisibleRows will trigger UIKit to call reloadData if it never has, which can result
// in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast.
if (CGRectEqualToRect(self.bounds, CGRectZero)) {
return @[];
}
NSArray *visibleIndexPaths = self.indexPathsForVisibleRows;
if (_pendingVisibleIndexPath) {

View File

@@ -60,6 +60,8 @@
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
[super interfaceStateDidChange:newState fromState:oldState];
if (!(newState & ASInterfaceStateVisible)) {
if (oldState & ASInterfaceStateVisible) {
if (_shouldBePlaying) {
@@ -231,6 +233,8 @@
- (void)visibilityDidChange:(BOOL)isVisible
{
[super visibilityDidChange:isVisible];
ASDN::MutexLocker l(_videoLock);
if (_shouldAutoplay && _playerNode.isNodeLoaded) {

View File

@@ -6,9 +6,19 @@
// Copyright © 2016 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASControlNode.h"
#import "ASImageNode.h"
@interface ASControlNode (Debugging)
/**
Class method to enable a visualization overlay of the tapable area on the ASControlNode. For app debugging purposes only.
@param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
*/
+ (void)setEnableHitTestDebug:(BOOL)enable;
@end
@interface ASImageNode (Debugging)
/**
@@ -20,4 +30,4 @@
+ (void)setShouldShowImageScalingOverlay:(BOOL)show;
+ (BOOL)shouldShowImageScalingOverlay;
@end
@end

View File

@@ -74,5 +74,6 @@
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
#import <AsyncDisplayKit/UIView+ASConvenience.h>
#import <AsyncDisplayKit/ASRunLoopQueue.h>
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>

View File

@@ -17,6 +17,19 @@ typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache);
@optional
/**
@abstract Attempts to fetch an image with the given URL from a memory cache.
@param URL The URL of the image to retrieve from the cache.
@discussion This method exists to support synchronous rendering of nodes. Before the layer is drawn, this method
is called to attempt to get the image out of the cache synchronously. This allows drawing to occur on the main thread
if displaysAsynchronously is set to NO or recursivelyEnsureDisplaySynchronously: has been called.
If `URL` is nil, `completion` will be invoked immediately with a nil image. This method *should* block
the calling thread to fetch the image from a fast memory cache. It is OK to return nil from this method and instead
support only cachedImageWithURL:callbackQueue:completion: however, synchronous rendering will not be possible.
*/
- (nullable UIImage *)synchronouslyFetchedCachedImageWithURL:(NSURL *)URL;
/**
@abstract Attempts to fetch an image with the given URL from the cache.
@param URL The URL of the image to retrieve from the cache.

View File

@@ -29,6 +29,13 @@
#pragma mark ASImageProtocols
- (UIImage *)synchronouslyFetchedCachedImageWithURL:(NSURL *)URL
{
NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil];
PINRemoteImageManagerResult *result = [[PINRemoteImageManager sharedImageManager] synchronousImageFromCacheWithCacheKey:key options:PINRemoteImageManagerDownloadOptionsSkipDecode];
return result.image;
}
- (void)fetchCachedImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
completion:(void (^)(CGImageRef imageFromCache))completion

View File

@@ -90,6 +90,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
{
_scrollDirection = scrollDirection;
// Perform update immediately, so that cells receive a visibilityDidChange: call before their first pixel is visible.
[self scheduleRangeUpdate];
}
@@ -149,6 +150,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
return;
}
// allNodes is a 2D array: it contains arrays for each section, each containing nodes.
NSArray<NSArray *> *allNodes = [_dataSource completedNodes];
NSUInteger numberOfSections = [allNodes count];
// TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges
// Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
NSArray<NSIndexPath *> *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
@@ -165,10 +170,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
}
// allNodes is a 2D array: it contains arrays for each section, each containing nodes.
NSArray<NSArray *> *allNodes = [_dataSource completedNodes];
NSUInteger numberOfSections = [allNodes count];
NSArray<ASDisplayNode *> *currentSectionNodes = nil;
NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration.
NSUInteger numberOfNodesInSection = 0;
@@ -237,6 +238,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
#if ASRangeControllerLoggingEnabled
ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]);
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
#endif
@@ -246,14 +248,15 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
if ([fetchDataIndexPaths containsObject:indexPath]) {
interfaceState |= ASInterfaceStateFetchData;
}
if ([displayIndexPaths containsObject:indexPath]) {
interfaceState |= ASInterfaceStateDisplay;
}
if ([visibleIndexPaths containsObject:indexPath]) {
interfaceState |= ASInterfaceStateVisible;
interfaceState |= (ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStateFetchData);
} else {
if ([fetchDataIndexPaths containsObject:indexPath]) {
interfaceState |= ASInterfaceStateFetchData;
}
if ([displayIndexPaths containsObject:indexPath]) {
interfaceState |= ASInterfaceStateDisplay;
}
}
} else {
// If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
@@ -490,19 +493,22 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible
+ (void)didReceiveMemoryWarning:(NSNotification *)notification
{
#if ASRangeControllerLoggingEnabled
NSLog(@"+[ASRangeController didReceiveMemoryWarning] with controllers: %@", [self allRangeControllersWeakSet]);
#endif
for (ASRangeController *rangeController in [self allRangeControllersWeakSet]) {
NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects];
for (ASRangeController *rangeController in allRangeControllers) {
BOOL isDisplay = ASInterfaceStateIncludesDisplay([rangeController interfaceState]);
[rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeMinimum : __rangeModeForMemoryWarnings];
[rangeController performRangeUpdate];
}
#if ASRangeControllerLoggingEnabled
NSLog(@"+[ASRangeController didReceiveMemoryWarning] with controllers: %@", allRangeControllers);
#endif
}
+ (void)didEnterBackground:(NSNotification *)notification
{
for (ASRangeController *rangeController in [self allRangeControllersWeakSet]) {
NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects];
for (ASRangeController *rangeController in allRangeControllers) {
// We do not want to fully collapse the Display ranges of any visible range controllers so that flashes can be avoided when
// the app is resumed. Non-visible controllers can be more aggressively culled to the LowMemory state (see definitions for documentation)
BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]);
@@ -511,27 +517,28 @@ static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeVisible
// Because -interfaceState checks __ApplicationState and always clears the "visible" bit if Backgrounded, we must set this after updating the range mode.
__ApplicationState = UIApplicationStateBackground;
for (ASRangeController *rangeController in [self allRangeControllersWeakSet]) {
for (ASRangeController *rangeController in allRangeControllers) {
// Trigger a range update immediately, as we may not be allowed by the system to run the update block scheduled by changing range mode.
[rangeController performRangeUpdate];
}
#if ASRangeControllerLoggingEnabled
NSLog(@"+[ASRangeController didEnterBackground] with controllers, after backgrounding: %@", [self allRangeControllersWeakSet]);
NSLog(@"+[ASRangeController didEnterBackground] with controllers, after backgrounding: %@", allRangeControllers);
#endif
}
+ (void)willEnterForeground:(NSNotification *)notification
{
NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects];
__ApplicationState = UIApplicationStateActive;
for (ASRangeController *rangeController in [self allRangeControllersWeakSet]) {
for (ASRangeController *rangeController in allRangeControllers) {
BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]);
[rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeMinimum : ASLayoutRangeModeVisibleOnly];
[rangeController performRangeUpdate];
}
#if ASRangeControllerLoggingEnabled
NSLog(@"+[ASRangeController willEnterForeground] with controllers, after foregrounding: %@", [self allRangeControllersWeakSet]);
NSLog(@"+[ASRangeController willEnterForeground] with controllers, after foregrounding: %@", allRangeControllers);
#endif
}

View File

@@ -30,6 +30,7 @@ typedef struct {
typedef struct {
CGSize min;
CGSize max;
int32_t transitionID;
} ASSizeRange;
extern ASRelativeDimension const ASRelativeDimensionUnconstrained;
@@ -58,6 +59,9 @@ extern CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat
extern ASSizeRange ASSizeRangeMake(CGSize min, CGSize max);
/** Creates an ASSizeRange with the provided size as both min and max */
extern ASSizeRange ASSizeRangeMakeExactSize(CGSize size);
/** Clamps the provided CGSize between the [min, max] bounds of this ASSizeRange. */
extern CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size);

View File

@@ -77,6 +77,11 @@ ASSizeRange ASSizeRangeMake(CGSize min, CGSize max)
ASSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange;
}
ASSizeRange ASSizeRangeMakeExactSize(CGSize size)
{
return ASSizeRangeMake(size, size);
}
CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size)
{
return CGSizeMake(MAX(sizeRange.min.width, MIN(sizeRange.max.width, size.width)),

View File

@@ -331,7 +331,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync,
// FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
// from the displayQueue? Need to not cancel early fails from displaySentinel changes.
ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil);
int64_t displaySentinelValue = [displaySentinel increment];
int32_t displaySentinelValue = [displaySentinel increment];
asdisplaynode_iscancelled_block_t isCancelledBlock = ^{
return BOOL(displaySentinelValue != displaySentinel.value);

View File

@@ -45,9 +45,17 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
ASHierarchyStateRangeManaged = 1 << 1,
/** Down-propogated version of _flags.visibilityNotificationsDisabled. This flag is very rarely set, but by having it
locally available to nodes, they do not have to walk up supernodes at the critical points it is checked. */
ASHierarchyStateTransitioningSupernodes = 1 << 2
ASHierarchyStateTransitioningSupernodes = 1 << 2,
/** One of the supernodes of this node is performing a transition.
Any layout calculated during this state should not be applied immediately, but pending until later. */
ASHierarchyStateLayoutPending = 1 << 3
};
inline BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateLayoutPending) == ASHierarchyStateLayoutPending);
}
@interface ASDisplayNode ()
{
@protected

View File

@@ -18,12 +18,14 @@
#import "ASThread.h"
#import "ASLayoutOptions.h"
#import "_ASTransitionContext.h"
#import "ASDisplayNodeLayoutContext.h"
#include <vector>
@protocol _ASDisplayLayerDelegate;
@class _ASDisplayLayer;
@class _ASPendingState;
@class ASSentinel;
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
@@ -68,6 +70,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
unsigned shouldRasterizeDescendants:1;
unsigned shouldBypassEnsureDisplay:1;
unsigned displaySuspended:1;
unsigned shouldAnimateSizeChanges:1;
unsigned hasCustomDrawingPriority:1;
// whether custom drawing is enabled
@@ -89,15 +92,13 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
ASDisplayNode * __weak _supernode;
ASSentinel *_displaySentinel;
ASSentinel *_replaceAsyncSentinel;
ASSentinel *_transitionSentinel;
// This is the desired contentsScale, not the scale at which the layer's contents should be displayed
CGFloat _contentsScaleForDisplay;
ASLayout *_previousLayout;
ASLayout *_layout;
ASSizeRange _previousConstrainedSize;
ASSizeRange _constrainedSize;
UIEdgeInsets _hitTestSlop;
@@ -107,11 +108,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
_ASTransitionContext *_transitionContext;
BOOL _usesImplicitHierarchyManagement;
NSArray<ASDisplayNode *> *_insertedSubnodes;
NSArray<ASDisplayNode *> *_removedSubnodes;
std::vector<NSInteger> _insertedSubnodePositions;
std::vector<NSInteger> _removedSubnodePositions;
int32_t _pendingTransitionID;
ASDisplayNodeLayoutContext *_pendingLayoutContext;
ASDisplayNodeViewBlock _viewBlock;
ASDisplayNodeLayerBlock _layerBlock;
ASDisplayNodeDidLoadBlock _nodeLoadedBlock;

View File

@@ -0,0 +1,33 @@
//
// ASDisplayNodeLayoutContext.h
// AsyncDisplayKit
//
// Created by Huy Nguyen on 3/8/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "ASDimension.h"
#import "_ASTransitionContext.h"
@class ASDisplayNode;
@class ASLayout;
@interface ASDisplayNodeLayoutContext : NSObject <_ASTransitionContextLayoutDelegate>
@property (nonatomic, readonly, weak) ASDisplayNode *node;
@property (nonatomic, readonly, strong) ASLayout *pendingLayout;
@property (nonatomic, readonly, assign) ASSizeRange pendingConstrainedSize;
@property (nonatomic, readonly, strong) ASLayout *previousLayout;
@property (nonatomic, readonly, assign) ASSizeRange previousConstrainedSize;
- (instancetype)initWithNode:(ASDisplayNode *)node
pendingLayout:(ASLayout *)pendingLayout
pendingConstrainedSize:(ASSizeRange)pendingConstrainedSize
previousLayout:(ASLayout *)previousLayout
previousConstrainedSize:(ASSizeRange)previousConstrainedSize;
- (void)applySubnodeInsertions;
- (void)applySubnodeRemovals;
@end

View File

@@ -0,0 +1,190 @@
//
// ASDisplayNodeLayoutContext.mm
// AsyncDisplayKit
//
// Created by Huy Nguyen on 3/8/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "ASDisplayNodeLayoutContext.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASLayout.h"
#import <vector>
#import "NSArray+Diffing.h"
#import "ASEqualityHelpers.h"
@implementation ASDisplayNodeLayoutContext {
ASDN::RecursiveMutex _propertyLock;
BOOL _calculatedSubnodeOperations;
NSArray<ASDisplayNode *> *_insertedSubnodes;
NSArray<ASDisplayNode *> *_removedSubnodes;
std::vector<NSInteger> _insertedSubnodePositions;
std::vector<NSInteger> _removedSubnodePositions;
}
- (instancetype)initWithNode:(ASDisplayNode *)node
pendingLayout:(ASLayout *)pendingLayout
pendingConstrainedSize:(ASSizeRange)pendingConstrainedSize
previousLayout:(ASLayout *)previousLayout
previousConstrainedSize:(ASSizeRange)previousConstrainedSize
{
self = [super init];
if (self) {
_node = node;
_pendingLayout = pendingLayout;
_pendingConstrainedSize = pendingConstrainedSize;
_previousLayout = previousLayout;
_previousConstrainedSize = previousConstrainedSize;
}
return self;
}
- (void)applySubnodeInsertions
{
ASDN::MutexLocker l(_propertyLock);
[self calculateSubnodeOperationsIfNeeded];
for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) {
NSInteger p = _insertedSubnodePositions[i];
[_node insertSubnode:_insertedSubnodes[i] atIndex:p];
}
}
- (void)applySubnodeRemovals
{
ASDN::MutexLocker l(_propertyLock);
[self calculateSubnodeOperationsIfNeeded];
for (NSInteger i = 0; i < [_removedSubnodes count]; i++) {
[_removedSubnodes[i] removeFromSupernode];
}
}
- (void)calculateSubnodeOperationsIfNeeded
{
ASDN::MutexLocker l(_propertyLock);
if (_calculatedSubnodeOperations) {
return;
}
if (_previousLayout) {
NSIndexSet *insertions, *deletions;
[_previousLayout.immediateSublayouts asdk_diffWithArray:_pendingLayout.immediateSublayouts
insertions:&insertions
deletions:&deletions
compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject);
}];
filterNodesInLayoutAtIndexes(_pendingLayout, insertions, &_insertedSubnodes, &_insertedSubnodePositions);
filterNodesInLayoutAtIndexesWithIntersectingNodes(_previousLayout,
deletions,
_insertedSubnodes,
&_removedSubnodes,
&_removedSubnodePositions);
} else {
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])];
filterNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions);
_removedSubnodes = nil;
}
_calculatedSubnodeOperations = YES;
}
#pragma mark - _ASTransitionContextDelegate
- (NSArray<ASDisplayNode *> *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context
{
ASDN::MutexLocker l(_propertyLock);
return _node.subnodes;
}
- (NSArray<ASDisplayNode *> *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context
{
ASDN::MutexLocker l(_propertyLock);
[self calculateSubnodeOperationsIfNeeded];
return _insertedSubnodes;
}
- (NSArray<ASDisplayNode *> *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context
{
ASDN::MutexLocker l(_propertyLock);
[self calculateSubnodeOperationsIfNeeded];
return _removedSubnodes;
}
- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key
{
ASDN::MutexLocker l(_propertyLock);
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
return _previousLayout;
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
return _pendingLayout;
} else {
return nil;
}
}
- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key
{
ASDN::MutexLocker l(_propertyLock);
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
return _previousConstrainedSize;
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
return _pendingConstrainedSize;
} else {
return ASSizeRangeMake(CGSizeZero, CGSizeZero);
}
}
#pragma mark - Filter helpers
/**
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
*/
static inline void filterNodesInLayoutAtIndexes(
ASLayout *layout,
NSIndexSet *indexes,
NSArray<ASDisplayNode *> * __strong *storedNodes,
std::vector<NSInteger> *storedPositions
)
{
filterNodesInLayoutAtIndexesWithIntersectingNodes(layout, indexes, nil, storedNodes, storedPositions);
}
/**
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
* @discussion If the node exists in the `intersectingNodes` array, the node is not added to `storedNodes`.
*/
static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes(
ASLayout *layout,
NSIndexSet *indexes,
NSArray<ASDisplayNode *> *intersectingNodes,
NSArray<ASDisplayNode *> * __strong *storedNodes,
std::vector<NSInteger> *storedPositions
)
{
NSMutableArray<ASDisplayNode *> *nodes = [NSMutableArray array];
std::vector<NSInteger> positions = std::vector<NSInteger>();
NSInteger idx = [indexes firstIndex];
while (idx != NSNotFound) {
BOOL skip = NO;
ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject;
ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts");
for (ASDisplayNode *i in intersectingNodes) {
if (node == i) {
skip = YES;
break;
}
}
if (!skip) {
[nodes addObject:node];
positions.push_back(idx);
}
idx = [indexes indexGreaterThanIndex:idx];
}
*storedNodes = nodes;
*storedPositions = positions;
}
@end

View File

@@ -27,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN
/// Removes all objects from the set.
- (void)removeAllObjects;
/// Returns a standard *retained* NSArray of all objects. Not free to generate, but useful for iterating over contents.
- (NSArray *)allObjects;
/**
How many objects are contained in this set.

View File

@@ -7,6 +7,7 @@
//
#import "ASWeakSet.h"
#import <Foundation/NSMapTable.h>
@interface ASWeakSet<__covariant ObjectType> ()
@property (nonatomic, strong, readonly) NSMapTable<ObjectType, NSNull *> *mapTable;
@@ -25,7 +26,7 @@
- (void)addObject:(id)object
{
[_mapTable setObject:[NSNull null] forKey:object];
[_mapTable setObject:(NSNull *)kCFNull forKey:object];
}
- (void)removeObject:(id)object
@@ -38,6 +39,22 @@
[_mapTable removeAllObjects];
}
- (NSArray *)allObjects
{
// We use keys instead of values in the map table for efficiency and better characteristics when the keys are deallocated.
// Documentation is currently unclear on whether -keyEnumerator retains its values, but does imply that modifying a
// mutable collection is still not safe while enumerating that way - which is one of the main uses for this method.
// A helper function called NSAllMapTableKeys() might do exactly what we want and should be more efficient, but unfortunately
// is throwing a strange compiler error and may not be available in practice on the latest iOS version.
// Lastly, even -dictionaryRepresentation and then -allKeys won't work, because it attemps to copy the values of each key,
// which may not support copying (such as ASRangeControllers).
NSMutableArray *allObjects = [NSMutableArray array];
for (id object in _mapTable) {
[allObjects addObject:object];
}
return allObjects;
}
- (BOOL)containsObject:(id)object
{
return [_mapTable objectForKey:object] != nil;
@@ -75,7 +92,7 @@
- (NSString *)description
{
return [[super description] stringByAppendingFormat:@" count: %lu, contents: %@", self.count, _mapTable];
return [[super description] stringByAppendingFormat:@" count: %lu, contents: %@", (unsigned long)self.count, _mapTable];
}
@end

View File

@@ -13,7 +13,7 @@
@class ASLayout;
@class _ASTransitionContext;
@protocol _ASTransitionContextDelegate <NSObject>
@protocol _ASTransitionContextLayoutDelegate <NSObject>
- (NSArray<ASDisplayNode *> *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context;
@@ -23,6 +23,10 @@
- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key;
- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key;
@end
@protocol _ASTransitionContextCompletionDelegate <NSObject>
- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete;
@end
@@ -31,6 +35,8 @@
@property (assign, readonly, nonatomic, getter=isAnimated) BOOL animated;
- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate;
- (instancetype)initWithAnimation:(BOOL)animated
layoutDelegate:(id<_ASTransitionContextLayoutDelegate>)layoutDelegate
completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate;
@end

View File

@@ -16,18 +16,22 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi
@interface _ASTransitionContext ()
@property (weak, nonatomic) id<_ASTransitionContextDelegate> delegate;
@property (weak, nonatomic) id<_ASTransitionContextLayoutDelegate> layoutDelegate;
@property (weak, nonatomic) id<_ASTransitionContextCompletionDelegate> completionDelegate;
@end
@implementation _ASTransitionContext
- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate
- (instancetype)initWithAnimation:(BOOL)animated
layoutDelegate:(id<_ASTransitionContextLayoutDelegate>)layoutDelegate
completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate
{
self = [super init];
if (self) {
_animated = animated;
_delegate = delegate;
_layoutDelegate = layoutDelegate;
_completionDelegate = completionDelegate;
}
return self;
}
@@ -36,17 +40,17 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi
- (ASLayout *)layoutForKey:(NSString *)key
{
return [_delegate transitionContext:self layoutForKey:key];
return [_layoutDelegate transitionContext:self layoutForKey:key];
}
- (ASSizeRange)constrainedSizeForKey:(NSString *)key
{
return [_delegate transitionContext:self constrainedSizeForKey:key];
return [_layoutDelegate transitionContext:self constrainedSizeForKey:key];
}
- (CGRect)initialFrameForNode:(ASDisplayNode *)node
{
for (ASDisplayNode *subnode in [_delegate currentSubnodesWithTransitionContext:self]) {
for (ASDisplayNode *subnode in [_layoutDelegate currentSubnodesWithTransitionContext:self]) {
if (node == subnode) {
return node.frame;
}
@@ -75,17 +79,17 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi
- (NSArray<ASDisplayNode *> *)insertedSubnodes
{
return [_delegate insertedSubnodesWithTransitionContext:self];
return [_layoutDelegate insertedSubnodesWithTransitionContext:self];
}
- (NSArray<ASDisplayNode *> *)removedSubnodes
{
return [_delegate removedSubnodesWithTransitionContext:self];
return [_layoutDelegate removedSubnodesWithTransitionContext:self];
}
- (void)completeTransition:(BOOL)didComplete
{
[_delegate transitionContext:self didComplete:didComplete];
[_completionDelegate transitionContext:self didComplete:didComplete];
}
@end

View File

@@ -1,5 +1,5 @@
//
// ASZBasicImageDownloaderTests.m
// ASBasicImageDownloaderTests.m
// AsyncDisplayKit
//
// Created by Victor Mayorov on 10/06/15.
@@ -10,7 +10,6 @@
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
// Z in the name to delay running until after the test instance is operating normally.
@interface ASBasicImageDownloaderTests : XCTestCase
@end
@@ -19,35 +18,30 @@
- (void)testAsynchronouslyDownloadTheSameURLTwice
{
ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader];
NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"];
XCTestExpectation *firstExpectation = [self expectationWithDescription:@"First ASBasicImageDownloader completion handler should be called within 3 seconds"];
XCTestExpectation *secondExpectation = [self expectationWithDescription:@"Second ASBasicImageDownloader completion handler should be called within 3 seconds"];
ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader];
NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgressBlock:nil
completion:^(CGImageRef image, NSError *error) {
[firstExpectation fulfill];
}];
__block BOOL firstDone = NO;
[downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgressBlock:nil
completion:^(CGImageRef image, NSError *error) {
firstDone = YES;
}];
__block BOOL secondDone = NO;
[downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgressBlock:nil
completion:^(CGImageRef image, NSError *error) {
secondDone = YES;
}];
[downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgressBlock:nil
completion:^(CGImageRef image, NSError *error) {
[secondExpectation fulfill];
}];
#pragma clang diagnostic pop
sleep(3);
XCTAssert(firstDone && secondDone, @"Not all ASBasicImageDownloader completion handlers have been called after 3 seconds");
[self waitForExpectationsWithTimeout:3 handler:nil];
}
@end

View File

@@ -278,7 +278,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
NSString *targetName = isLayerBacked ? @"layer" : @"view";
NSString *hasLoadedView = node.nodeLoaded ? @"with view" : [NSString stringWithFormat:@"after loading %@", targetName];
id rgbBlackCGColorIdPtr = (id)[UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor;
// id rgbBlackCGColorIdPtr = (id)[UIColor blackColor].CGColor;
XCTAssertEqual((id)nil, node.contents, @"default contents broken %@", hasLoadedView);
XCTAssertEqual(NO, node.clipsToBounds, @"default clipsToBounds broken %@", hasLoadedView);
@@ -298,12 +298,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
XCTAssertTrue(CATransform3DEqualToTransform(CATransform3DIdentity, node.subnodeTransform), @"default subnodeTransform broken %@", hasLoadedView);
XCTAssertEqual((id)nil, node.backgroundColor, @"default backgroundColor broken %@", hasLoadedView);
XCTAssertEqual(UIViewContentModeScaleToFill, node.contentMode, @"default contentMode broken %@", hasLoadedView);
XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.shadowColor, @"default shadowColor broken %@", hasLoadedView);
// XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.shadowColor, @"default shadowColor broken %@", hasLoadedView);
XCTAssertEqual(0.0f, node.shadowOpacity, @"default shadowOpacity broken %@", hasLoadedView);
XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(0, -3), node.shadowOffset), @"default shadowOffset broken %@", hasLoadedView);
XCTAssertEqual(3.f, node.shadowRadius, @"default shadowRadius broken %@", hasLoadedView);
XCTAssertEqual(0.0f, node.borderWidth, @"default borderWidth broken %@", hasLoadedView);
XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.borderColor, @"default borderColor broken %@", hasLoadedView);
// XCTAssertEqualObjects(rgbBlackCGColorIdPtr, (id)node.borderColor, @"default borderColor broken %@", hasLoadedView);
XCTAssertEqual(NO, node.displaySuspended, @"default displaySuspended broken %@", hasLoadedView);
XCTAssertEqual(YES, node.displaysAsynchronously, @"default displaysAsynchronously broken %@", hasLoadedView);
XCTAssertEqual(NO, node.asyncdisplaykit_asyncTransactionContainer, @"default asyncdisplaykit_asyncTransactionContainer broken %@", hasLoadedView);

View File

@@ -140,7 +140,7 @@
_videoNode.asset = _firstAsset;
[_videoNode pause];
[_videoNode setInterfaceState:ASInterfaceStateVisible];
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay];
[_videoNode didLoad];
XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]);