mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-03-27 17:20:01 +00:00
Merge commit '192e9398e53685be52e0eab26501a1f2a2926cdd'
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
33
AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.h
Normal file
33
AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.h
Normal 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
|
||||
190
AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm
Normal file
190
AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm
Normal 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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
_videoNode.asset = _firstAsset;
|
||||
|
||||
[_videoNode pause];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay];
|
||||
[_videoNode didLoad];
|
||||
|
||||
XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]);
|
||||
|
||||
Reference in New Issue
Block a user