Swiftgram/AsyncDisplayKit/Details/ASRangeController.mm
James Ide b5d999ab55 Fix ASRangeController's delegate method invocations
"did" should be "will" in a couple of places
2015-01-21 17:07:53 -08:00

310 lines
11 KiB
Plaintext

/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASRangeController.h"
#import "ASAssert.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h"
#import "ASLayoutController.h"
#import "ASMultiDimensionalArrayUtils.h"
@interface ASDisplayNode (ASRangeController)
- (void)display;
- (void)recursivelyDisplay;
@end
@implementation ASDisplayNode (ASRangeController)
- (void)display
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(self.nodeLoaded, @"backing store must be loaded before calling -display");
CALayer *layer = self.layer;
// rendering a backing store requires a node be laid out
[layer setNeedsLayout];
[layer layoutIfNeeded];
if (layer.contents) {
return;
}
[layer setNeedsDisplay];
[layer displayIfNeeded];
}
- (void)recursivelyDisplay
{
for (ASDisplayNode *node in self.subnodes) {
[node recursivelyDisplay];
}
[self display];
}
@end
@interface ASRangeController () {
NSSet *_workingRangeIndexPaths;
NSSet *_workingRangeNodes;
BOOL _workingRangeIsValid;
BOOL _queuedRangeUpdate;
ASScrollDirection _scrollDirection;
}
@end
@implementation ASRangeController
- (instancetype)init {
if (self = [super init]) {
_workingRangeIndexPaths = [NSSet set];
_workingRangeIsValid = YES;
}
return self;
}
#pragma mark - View manipulation.
- (void)discardNode:(ASCellNode *)node
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"invalid argument");
if ([_workingRangeNodes containsObject:node]) {
// move the node's view to the working range area, so its rendering persists
[self addNodeToWorkingRange:node];
} else {
// this node isn't in the working range, remove it from the view hierarchy
[self removeNodeFromWorkingRange:node];
}
}
- (void)removeNodeFromWorkingRange:(ASCellNode *)node
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"invalid argument");
[node recursivelySetDisplaySuspended:YES];
[node.view removeFromSuperview];
// since this class usually manages large or infinite data sets, the working range
// directly bounds memory usage by requiring redrawing any content that falls outside the range.
[node recursivelyReclaimMemory];
}
- (void)addNodeToWorkingRange:(ASCellNode *)node
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"invalid argument");
// if node is in the working range it should not actively be in view
[node.view removeFromSuperview];
[node recursivelyDisplay];
}
- (void)moveNode:(ASCellNode *)node toView:(UIView *)view
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node && view, @"invalid argument, did you mean -removeNodeFromWorkingRange:?");
[view addSubview:node.view];
}
#pragma mark -
#pragma mark API.
- (void)visibleNodeIndexPathsDidChangeWithScrollDirection:(ASScrollDirection)scrollDirection
{
_scrollDirection = scrollDirection;
if (_queuedRangeUpdate) {
return;
}
// coalesce these events -- handling them multiple times per runloop is noisy and expensive
_queuedRangeUpdate = YES;
[self performSelector:@selector(updateVisibleNodeIndexPaths)
withObject:nil
afterDelay:0
inModes:@[ NSRunLoopCommonModes ]];
}
- (void)updateVisibleNodeIndexPaths
{
if (!_queuedRangeUpdate) {
return;
}
NSArray *indexPaths = [_delegate rangeControllerVisibleNodeIndexPaths:self];
CGSize viewportSize = [_delegate rangeControllerViewportSize:self];
if ([_layoutController shouldUpdateWorkingRangesForVisibleIndexPath:indexPaths viewportSize:viewportSize]) {
[_layoutController setVisibleNodeIndexPaths:indexPaths];
NSSet *workingRangeIndexPaths = [_layoutController workingRangeIndexPathsForScrolling:_scrollDirection viewportSize:viewportSize];
NSSet *visibleRangeIndexPaths = [NSSet setWithArray:indexPaths];
NSMutableSet *removedIndexPaths = _workingRangeIsValid ? [_workingRangeIndexPaths mutableCopy] : [NSMutableSet set];
[removedIndexPaths minusSet:workingRangeIndexPaths];
[removedIndexPaths minusSet:visibleRangeIndexPaths];
if (removedIndexPaths.count) {
NSArray *removedNodes = [_delegate rangeController:self nodesAtIndexPaths:[removedIndexPaths allObjects]];
[removedNodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
[self removeNodeFromWorkingRange:node];
}];
}
NSMutableSet *addedIndexPaths = [workingRangeIndexPaths mutableCopy];
[addedIndexPaths minusSet:_workingRangeIndexPaths];
[addedIndexPaths minusSet:visibleRangeIndexPaths];
if (addedIndexPaths.count) {
NSArray *addedNodes = [_delegate rangeController:self nodesAtIndexPaths:[addedIndexPaths allObjects]];
[addedNodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
[self addNodeToWorkingRange:node];
}];
}
_workingRangeIndexPaths = workingRangeIndexPaths;
_workingRangeNodes = [NSSet setWithArray:[_delegate rangeController:self nodesAtIndexPaths:[workingRangeIndexPaths allObjects]]];
_workingRangeIsValid = YES;
}
_queuedRangeUpdate = NO;
}
- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)cellNode
{
[cellNode recursivelySetDisplaySuspended:NO];
if (cellNode.view.superview == contentView) {
// this content view is already correctly configured
return;
}
for (UIView *view in contentView.subviews) {
ASDisplayNode *node = view.asyncdisplaykit_node;
if (node) {
// plunk this node back into the working range, if appropriate
ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"invalid node");
[self discardNode:(ASCellNode *)node];
} else {
// if it's not a node, it's something random UITableView added to the hierarchy. kill it.
[view removeFromSuperview];
}
}
[self moveNode:cellNode toView:contentView];
}
#pragma mark - ASDataControllerDelegete
- (void)dataControllerBeginUpdates:(ASDataController *)dataController {
ASDisplayNodePerformBlockOnMainThread(^{
[_delegate rangeControllerBeginUpdates:self];
});
}
- (void)dataControllerEndUpdates:(ASDataController *)dataController {
ASDisplayNodePerformBlockOnMainThread(^{
[_delegate rangeControllerEndUpdates:self];
});
}
- (void)dataController:(ASDataController *)dataController willInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodePerformBlockOnMainThread(^{
if ([_delegate respondsToSelector:@selector(rangeController:willInsertNodesAtIndexPaths:withAnimationOption:)]) {
[_delegate rangeController:self willInsertNodesAtIndexPaths:indexPaths withAnimationOption:animationOption];
}
});
}
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count];
[nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx, BOOL *stop) {
[nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]];
}];
ASDisplayNodePerformBlockOnMainThread(^{
[_layoutController insertNodesAtIndexPaths:indexPaths withSizes:nodeSizes];
[_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOption:animationOption];
_workingRangeIsValid = NO;
});
}
- (void)dataController:(ASDataController *)dataController willDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodePerformBlockOnMainThread(^{
if ([_delegate respondsToSelector:@selector(rangeController:willDeleteNodesAtIndexPaths:withAnimationOption:)]) {
[_delegate rangeController:self willDeleteNodesAtIndexPaths:indexPaths withAnimationOption:animationOption];
}
});
}
- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodePerformBlockOnMainThread(^{
[_layoutController deleteNodesAtIndexPaths:indexPaths];
[_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOption:animationOption];
_workingRangeIsValid = NO;
});
}
- (void)dataController:(ASDataController *)dataController willInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodePerformBlockOnMainThread(^{
if ([_delegate respondsToSelector:@selector(rangeController:willInsertSectionsAtIndexSet:withAnimationOption:)]) {
[_delegate rangeController:self willInsertSectionsAtIndexSet:indexSet withAnimationOption:animationOption];
}
});
}
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections");
NSMutableArray *sectionNodeSizes = [NSMutableArray arrayWithCapacity:sections.count];
[sections enumerateObjectsUsingBlock:^(NSArray *nodes, NSUInteger idx, BOOL *stop) {
NSMutableArray *nodeSizes = [NSMutableArray arrayWithCapacity:nodes.count];
[nodes enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger idx2, BOOL *stop2) {
[nodeSizes addObject:[NSValue valueWithCGSize:node.calculatedSize]];
}];
[sectionNodeSizes addObject:nodeSizes];
}];
ASDisplayNodePerformBlockOnMainThread(^{
[_layoutController insertSections:sectionNodeSizes atIndexSet:indexSet];
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOption:animationOption];
_workingRangeIsValid = NO;
});
}
- (void)dataController:(ASDataController *)dataController willDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodePerformBlockOnMainThread(^{
[_delegate rangeController:self willDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption];
});
}
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption {
ASDisplayNodePerformBlockOnMainThread(^{
[_layoutController deleteSectionsAtIndexSet:indexSet];
[_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption];
_workingRangeIsValid = NO;
});
}
@end