mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
ASCollectionView doesn't animate size changes if some of the updated cell nodes don't want to
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
#import "ASCollectionViewLayoutController.h"
|
#import "ASCollectionViewLayoutController.h"
|
||||||
#import "ASCollectionViewFlowLayoutInspector.h"
|
#import "ASCollectionViewFlowLayoutInspector.h"
|
||||||
#import "ASCollectionViewLayoutFacilitatorProtocol.h"
|
#import "ASCollectionViewLayoutFacilitatorProtocol.h"
|
||||||
|
#import "ASDisplayNodeExtras.h"
|
||||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||||
#import "ASDisplayNode+Beta.h"
|
#import "ASDisplayNode+Beta.h"
|
||||||
#import "ASInternalHelpers.h"
|
#import "ASInternalHelpers.h"
|
||||||
@@ -57,6 +58,36 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
|||||||
|
|
||||||
@end
|
@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 -
|
||||||
#pragma mark ASCollectionView.
|
#pragma mark ASCollectionView.
|
||||||
|
|
||||||
@@ -78,7 +109,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
|||||||
BOOL _asyncDelegateImplementsScrollviewDidScroll;
|
BOOL _asyncDelegateImplementsScrollviewDidScroll;
|
||||||
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
|
BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
|
||||||
BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath;
|
BOOL _asyncDataSourceImplementsNodeBlockForItemAtIndexPath;
|
||||||
BOOL _queuedNodeSizeUpdate;
|
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
|
||||||
BOOL _isDeallocating;
|
BOOL _isDeallocating;
|
||||||
|
|
||||||
ASBatchContext *_batchContext;
|
ASBatchContext *_batchContext;
|
||||||
@@ -1018,24 +1049,61 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
|||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
|
|
||||||
if (!sizeChanged || _queuedNodeSizeUpdate) {
|
if (!sizeChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_queuedNodeSizeUpdate = YES;
|
BOOL queued = (_queuedNodeSizeInvalidationContext != nil);
|
||||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[[self indexPathForNode:node]] batched:NO];
|
if (!queued) {
|
||||||
[self performSelector:@selector(requeryNodeSizes)
|
_queuedNodeSizeInvalidationContext = [[_ASCollectionViewNodeSizeInvalidationContext alloc] init];
|
||||||
withObject:nil
|
|
||||||
afterDelay:0
|
__weak __typeof__(self) weakSelf = self;
|
||||||
inModes:@[ NSRunLoopCommonModes ]];
|
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
|
// Cause UICollectionView to requery for the new size of all nodes
|
||||||
- (void)requeryNodeSizes
|
- (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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
[super performBatchUpdates:^{} completion:nil];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_queuedNodeSizeInvalidationContext = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Memory Management
|
#pragma mark - Memory Management
|
||||||
|
|||||||
@@ -428,6 +428,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) BOOL displaySuspended;
|
@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.
|
* @abstract Prevent the node and its descendants' layer from displaying.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *i
|
|||||||
|
|
||||||
flags.isInHierarchy = NO;
|
flags.isInHierarchy = NO;
|
||||||
flags.displaysAsynchronously = YES;
|
flags.displaysAsynchronously = YES;
|
||||||
|
flags.shouldAnimateSizeChanges = YES;
|
||||||
flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
|
flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0);
|
||||||
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
|
flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
@@ -2502,6 +2503,20 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (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";
|
static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
|
||||||
|
|
||||||
- (void)setDrawingPriority:(NSInteger)drawingPriority
|
- (void)setDrawingPriority:(NSInteger)drawingPriority
|
||||||
|
|||||||
@@ -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.
|
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.
|
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.
|
* 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);
|
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));
|
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;
|
CALayer *layer = node.layer;
|
||||||
|
|
||||||
@@ -68,9 +68,9 @@ id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
|
|||||||
return nil;
|
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];
|
return [n isKindOfClass:c];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,10 +128,10 @@ extern NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNo
|
|||||||
|
|
||||||
#pragma mark - Find first subnode
|
#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) {
|
for (ASDisplayNode *subnode in startNode.subnodes) {
|
||||||
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstSubnode(subnode, YES, block);
|
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block);
|
||||||
if (foundNode) {
|
if (foundNode) {
|
||||||
return foundNode;
|
return foundNode;
|
||||||
}
|
}
|
||||||
@@ -143,9 +143,14 @@ static ASDisplayNode *_ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, B
|
|||||||
return nil;
|
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))
|
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)
|
extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c)
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
unsigned shouldRasterizeDescendants:1;
|
unsigned shouldRasterizeDescendants:1;
|
||||||
unsigned shouldBypassEnsureDisplay:1;
|
unsigned shouldBypassEnsureDisplay:1;
|
||||||
unsigned displaySuspended:1;
|
unsigned displaySuspended:1;
|
||||||
|
unsigned shouldAnimateSizeChanges:1;
|
||||||
unsigned hasCustomDrawingPriority:1;
|
unsigned hasCustomDrawingPriority:1;
|
||||||
|
|
||||||
// whether custom drawing is enabled
|
// whether custom drawing is enabled
|
||||||
|
|||||||
Reference in New Issue
Block a user