mirror of
https://github.com/Instagram/IGListKit
synced 2026-05-23 09:18:29 +00:00
Add experimental collectionView getter fix
Summary: Adding a fix to the `IGListAdapterUpdater` that requests the `UICollectionView` to perform updates on until just-before we update. This way if the `UICollectionView` is changed between update-queue and execution (b/c updates are async), we guarantee the update is performed on the correct view. See the accompanying unit test that fails w/out the fix enabled. Differential Revision: D7889908 fbshipit-source-id: 7178677f34951a1e42986b0289fc4abc708d6946
This commit is contained in:
parent
cce5a462bb
commit
583efb936b
9 changed files with 224 additions and 134 deletions
|
|
@ -5,6 +5,12 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag
|
|||
4.0.0 (upcoming release)
|
||||
-----
|
||||
|
||||
### Enhancements
|
||||
|
||||
### Fixes
|
||||
|
||||
- Experimental fix to get the `UICollectionView` for batch updating immediately before applying the update. [Ryan Nystrom](https://github.com/rnystrom) (tbd)
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ typedef NS_OPTIONS (NSInteger, IGListExperiment) {
|
|||
IGListExperimentDedupeItemUpdates = 1 << 5,
|
||||
/// Test deferring object creation until just before diffing.
|
||||
IGListExperimentDeferredToObjectCreation = 1 << 6,
|
||||
/// Test getting collection view at update time.
|
||||
IGListExperimentGetCollectionViewAtUpdate = 1 << 7,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@
|
|||
_registeredSupplementaryViewIdentifiers = [NSMutableSet new];
|
||||
_registeredSupplementaryViewNibNames = [NSMutableSet new];
|
||||
|
||||
const BOOL settingFirstCollectionView = _collectionView == nil;
|
||||
|
||||
_collectionView = collectionView;
|
||||
_collectionView.dataSource = self;
|
||||
|
||||
|
|
@ -110,7 +112,12 @@
|
|||
[_collectionView.collectionViewLayout invalidateLayout];
|
||||
|
||||
[self _updateCollectionViewDelegate];
|
||||
[self _updateAfterPublicSettingsChange];
|
||||
|
||||
// only construct
|
||||
if (!IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate)
|
||||
|| settingFirstCollectionView) {
|
||||
[self _updateAfterPublicSettingsChange];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,11 +200,11 @@
|
|||
[collectionView layoutIfNeeded];
|
||||
|
||||
NSIndexPath *indexPathFirstElement = [NSIndexPath indexPathForItem:0 inSection:section];
|
||||
|
||||
|
||||
// collect the layout attributes for the cell and supplementary views for the first index
|
||||
// this will break if there are supplementary views beyond item 0
|
||||
NSMutableArray<UICollectionViewLayoutAttributes *> *attributes = nil;
|
||||
|
||||
|
||||
const NSInteger numberOfItems = [collectionView numberOfItemsInSection:section];
|
||||
if (numberOfItems > 0) {
|
||||
attributes = [self _layoutAttributesForIndexPath:indexPathFirstElement supplementaryKinds:supplementaryKinds].mutableCopy;
|
||||
|
|
@ -314,7 +321,7 @@
|
|||
id<IGListAdapterDataSource> dataSource = self.dataSource;
|
||||
UICollectionView *collectionView = self.collectionView;
|
||||
if (dataSource == nil || collectionView == nil) {
|
||||
IGLKLog(@"Warning: Your call to %s is ignored as dataSource or collectionView haven't been set.", __PRETTY_FUNCTION__);
|
||||
IGLKLog(@"Warning: Your call to %s is ignored as dataSource or collectionView haven't been set.", __PRETTY_FUNCTION__);
|
||||
if (completion) {
|
||||
completion(NO);
|
||||
}
|
||||
|
|
@ -341,26 +348,26 @@
|
|||
}
|
||||
|
||||
[self _enterBatchUpdates];
|
||||
[self.updater performUpdateWithCollectionView:collectionView
|
||||
fromObjects:fromObjects
|
||||
toObjectsBlock:toObjectsBlock
|
||||
animated:animated
|
||||
objectTransitionBlock:^(NSArray *toObjects) {
|
||||
// temporarily capture the item map that we are transitioning from in case
|
||||
// there are any item deletes at the same
|
||||
weakSelf.previousSectionMap = [weakSelf.sectionMap copy];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock]
|
||||
fromObjects:fromObjects
|
||||
toObjectsBlock:toObjectsBlock
|
||||
animated:animated
|
||||
objectTransitionBlock:^(NSArray *toObjects) {
|
||||
// temporarily capture the item map that we are transitioning from in case
|
||||
// there are any item deletes at the same
|
||||
weakSelf.previousSectionMap = [weakSelf.sectionMap copy];
|
||||
|
||||
[weakSelf _updateObjects:toObjects dataSource:dataSource];
|
||||
} completion:^(BOOL finished) {
|
||||
// release the previous items
|
||||
weakSelf.previousSectionMap = nil;
|
||||
[weakSelf _updateObjects:toObjects dataSource:dataSource];
|
||||
} completion:^(BOOL finished) {
|
||||
// release the previous items
|
||||
weakSelf.previousSectionMap = nil;
|
||||
|
||||
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
|
||||
if (completion) {
|
||||
completion(finished);
|
||||
}
|
||||
[weakSelf _exitBatchUpdates];
|
||||
}];
|
||||
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
|
||||
if (completion) {
|
||||
completion(finished);
|
||||
}
|
||||
[weakSelf _exitBatchUpdates];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion {
|
||||
|
|
@ -379,16 +386,17 @@
|
|||
NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[self.updater reloadDataWithCollectionView:collectionView reloadUpdateBlock:^{
|
||||
// purge all section controllers from the item map so that they are regenerated
|
||||
[weakSelf.sectionMap reset];
|
||||
[weakSelf _updateObjects:uniqueObjects dataSource:dataSource];
|
||||
} completion:^(BOOL finished) {
|
||||
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO];
|
||||
if (completion) {
|
||||
completion(finished);
|
||||
}
|
||||
}];
|
||||
[self.updater reloadDataWithCollectionViewBlock:[self _collectionViewBlock]
|
||||
reloadUpdateBlock:^{
|
||||
// purge all section controllers from the item map so that they are regenerated
|
||||
[weakSelf.sectionMap reset];
|
||||
[weakSelf _updateObjects:uniqueObjects dataSource:dataSource];
|
||||
} completion:^(BOOL finished) {
|
||||
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO];
|
||||
if (completion) {
|
||||
completion(finished);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reloadObjects:(NSArray *)objects {
|
||||
|
|
@ -447,7 +455,7 @@
|
|||
|
||||
- (nullable IGListSectionController *)sectionControllerForSection:(NSInteger)section {
|
||||
IGAssertMainThread();
|
||||
|
||||
|
||||
return [self.sectionMap sectionControllerForSection:section];
|
||||
}
|
||||
|
||||
|
|
@ -586,6 +594,16 @@
|
|||
|
||||
#pragma mark - Private API
|
||||
|
||||
- (IGListCollectionViewBlock)_collectionViewBlock {
|
||||
if (IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate)) {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
return ^UICollectionView *{ return weakSelf.collectionView; };
|
||||
} else {
|
||||
__weak UICollectionView *collectionView = _collectionView;
|
||||
return ^UICollectionView *{ return collectionView; };
|
||||
}
|
||||
}
|
||||
|
||||
// this method is what updates the "source of truth"
|
||||
// this should only be called just before the collection view is updated
|
||||
- (void)_updateObjects:(NSArray *)objects dataSource:(id<IGListAdapterDataSource>)dataSource {
|
||||
|
|
@ -637,7 +655,7 @@
|
|||
[sectionControllers addObject:sectionController];
|
||||
[validObjects addObject:object];
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
IGAssert([NSSet setWithArray:sectionControllers].count == sectionControllers.count,
|
||||
@"Section controllers array is not filled with unique objects; section controllers are being reused");
|
||||
|
|
@ -725,7 +743,7 @@
|
|||
}
|
||||
|
||||
- (NSArray<UICollectionViewLayoutAttributes *> *)_layoutAttributesForIndexPath:(NSIndexPath *)indexPath
|
||||
supplementaryKinds:(NSArray<NSString *> *)supplementaryKinds {
|
||||
supplementaryKinds:(NSArray<NSString *> *)supplementaryKinds {
|
||||
UICollectionViewLayout *layout = self.collectionView.collectionViewLayout;
|
||||
NSMutableArray<UICollectionViewLayoutAttributes *> *attributes = [NSMutableArray new];
|
||||
|
||||
|
|
@ -933,7 +951,7 @@
|
|||
- (void)selectItemAtIndex:(NSInteger)index
|
||||
sectionController:(IGListSectionController *)sectionController
|
||||
animated:(BOOL)animated
|
||||
scrollPosition:(UICollectionViewScrollPosition)scrollPosition {
|
||||
scrollPosition:(UICollectionViewScrollPosition)scrollPosition {
|
||||
IGAssertMainThread();
|
||||
IGParameterAssert(sectionController != nil);
|
||||
NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO];
|
||||
|
|
@ -941,9 +959,9 @@
|
|||
}
|
||||
|
||||
- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
|
||||
withReuseIdentifier:(NSString *)reuseIdentifier
|
||||
forSectionController:(IGListSectionController *)sectionController
|
||||
atIndex:(NSInteger)index {
|
||||
withReuseIdentifier:(NSString *)reuseIdentifier
|
||||
forSectionController:(IGListSectionController *)sectionController
|
||||
atIndex:(NSInteger)index {
|
||||
IGAssertMainThread();
|
||||
IGParameterAssert(sectionController != nil);
|
||||
IGParameterAssert(cellClass != nil);
|
||||
|
|
@ -1057,9 +1075,9 @@
|
|||
IGAssert(collectionView != nil, @"Performing batch updates without a collection view.");
|
||||
|
||||
[self _enterBatchUpdates];
|
||||
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[self.updater performUpdateWithCollectionView:collectionView animated:animated itemUpdates:^{
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock] animated:animated itemUpdates:^{
|
||||
weakSelf.isInUpdateBlock = YES;
|
||||
// the adapter acts as the batch context with its API stripped to just the IGListBatchContext protocol
|
||||
updates(weakSelf);
|
||||
|
|
@ -1080,7 +1098,7 @@
|
|||
animated:(BOOL)animated {
|
||||
IGAssertMainThread();
|
||||
IGParameterAssert(sectionController != nil);
|
||||
|
||||
|
||||
NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index usePreviousIfInUpdateBlock:NO];
|
||||
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
|
||||
}
|
||||
|
|
@ -1089,16 +1107,16 @@
|
|||
completion:(void (^)(BOOL finished))completion{
|
||||
const NSInteger section = [self sectionForSectionController:sectionController];
|
||||
const NSInteger items = [_collectionView numberOfItemsInSection:section];
|
||||
|
||||
|
||||
NSMutableArray<NSIndexPath *> *indexPaths = [NSMutableArray new];
|
||||
for (NSInteger item = 0; item < items; item++) {
|
||||
[indexPaths addObject:[NSIndexPath indexPathForItem:item inSection:section]];
|
||||
}
|
||||
|
||||
|
||||
UICollectionViewLayout *layout = _collectionView.collectionViewLayout;
|
||||
UICollectionViewLayoutInvalidationContext *context = [[[layout.class invalidationContextClass] alloc] init];
|
||||
[context invalidateItemsAtIndexPaths:indexPaths];
|
||||
|
||||
|
||||
__weak __typeof__(_collectionView) weakCollectionView = _collectionView;
|
||||
|
||||
// do not call -[UICollectionView performBatchUpdates:completion:] while already updating. defer it until completed.
|
||||
|
|
@ -1228,7 +1246,7 @@
|
|||
IGAssert(collectionView != nil, @"Moving section %@ without a collection view from index %li to index %li.",
|
||||
sectionController, (long)fromIndex, (long)toIndex);
|
||||
IGAssert(self.moveDelegate != nil, @"Moving section %@ without a moveDelegate set", sectionController);
|
||||
|
||||
|
||||
if (fromIndex != toIndex) {
|
||||
id<IGListAdapterDataSource> dataSource = self.dataSource;
|
||||
|
||||
|
|
@ -1250,18 +1268,18 @@
|
|||
|
||||
// inform the data source to update its model
|
||||
[self.moveDelegate listAdapter:self moveObject:object from:previousObjects to:objects];
|
||||
|
||||
|
||||
// update our model based on that provided by the data source
|
||||
NSArray<id<IGListDiffable>> *updatedObjects = [dataSource objectsForListAdapter:self];
|
||||
[self _updateObjects:updatedObjects dataSource:dataSource];
|
||||
}
|
||||
|
||||
|
||||
// even if from and to index are equal, we need to perform the "move"
|
||||
// iOS interactively moves items, not sections, so we might have actually moved the item
|
||||
// to the end of the preceeding section or beginning of the following section
|
||||
[self.updater moveSectionInCollectionView:collectionView fromIndex:fromIndex toIndex:toIndex];
|
||||
}
|
||||
|
||||
|
||||
- (void)moveInSectionControllerInteractive:(IGListSectionController *)sectionController
|
||||
fromIndex:(NSInteger)fromIndex
|
||||
toIndex:(NSInteger)toIndex NS_AVAILABLE_IOS(9_0) {
|
||||
|
|
@ -1269,7 +1287,7 @@
|
|||
IGParameterAssert(sectionController != nil);
|
||||
IGParameterAssert(fromIndex >= 0);
|
||||
IGParameterAssert(toIndex >= 0);
|
||||
|
||||
|
||||
[sectionController moveObjectFromIndex:fromIndex toIndex:toIndex];
|
||||
}
|
||||
|
||||
|
|
@ -1278,9 +1296,10 @@
|
|||
UICollectionView *collectionView = self.collectionView;
|
||||
IGAssert(collectionView != nil, @"Reverting move without a collection view from %@ to %@.",
|
||||
sourceIndexPath, destinationIndexPath);
|
||||
|
||||
|
||||
// revert by moving back in the opposite direction
|
||||
[collectionView moveItemAtIndexPath:destinationIndexPath toIndexPath:sourceIndexPath];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
|||
|
|
@ -42,10 +42,11 @@
|
|||
|| self.toObjectsBlock != nil;
|
||||
}
|
||||
|
||||
- (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView {
|
||||
- (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
|
||||
IGAssertMainThread();
|
||||
|
||||
// bail early if the collection view has been deallocated in the time since the update was queued
|
||||
UICollectionView *collectionView = collectionViewBlock();
|
||||
if (collectionView == nil) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -92,11 +93,12 @@
|
|||
self.state = IGListBatchUpdateStateIdle;
|
||||
}
|
||||
|
||||
- (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView {
|
||||
- (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
|
||||
IGAssertMainThread();
|
||||
IGAssert(self.state == IGListBatchUpdateStateIdle, @"Should not call batch updates when state isn't idle");
|
||||
|
||||
// bail early if the collection view has been deallocated in the time since the update was queued
|
||||
UICollectionView *collectionView = collectionViewBlock();
|
||||
if (collectionView == nil) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -208,11 +210,13 @@
|
|||
|
||||
// queue another update in case something changed during batch updates. this method will bail next runloop if
|
||||
// there are no changes
|
||||
[self _queueUpdateWithCollectionView:collectionView];
|
||||
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
|
||||
};
|
||||
|
||||
// block that executes the batch update and exception handling
|
||||
void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){
|
||||
[collectionView layoutIfNeeded];
|
||||
|
||||
@try {
|
||||
[delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView];
|
||||
if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) {
|
||||
|
|
@ -369,16 +373,16 @@ void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
self.batchUpdates = [IGListBatchUpdates new];
|
||||
}
|
||||
|
||||
- (void)_queueUpdateWithCollectionView:(UICollectionView *)collectionView {
|
||||
- (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
|
||||
IGAssertMainThread();
|
||||
|
||||
// callers may hold weak refs and lose the collection view by the time we requeue, bail if that's the case
|
||||
if (collectionView == nil) {
|
||||
return;
|
||||
}
|
||||
// if (collectionView == nil) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
__weak __typeof__(collectionView) weakCollectionView = collectionView;
|
||||
// __weak __typeof__(collectionView) weakCollectionView = collectionView;
|
||||
|
||||
// dispatch after a given amount of time to coalesce other updates and execute as one
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, self.coalescanceTime * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
|
|
@ -388,9 +392,9 @@ void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
}
|
||||
|
||||
if (weakSelf.hasQueuedReloadData) {
|
||||
[weakSelf performReloadDataWithCollectionView:weakCollectionView];
|
||||
[weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock];
|
||||
} else {
|
||||
[weakSelf performBatchUpdatesWithCollectionView:weakCollectionView];
|
||||
[weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -418,14 +422,14 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
return functions;
|
||||
}
|
||||
|
||||
- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView
|
||||
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
fromObjects:(NSArray *)fromObjects
|
||||
toObjectsBlock:(IGListToObjectBlock)toObjectsBlock
|
||||
animated:(BOOL)animated
|
||||
objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
|
||||
completion:(IGListUpdatingCompletion)completion {
|
||||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(collectionViewBlock != nil);
|
||||
IGParameterAssert(objectTransitionBlock != nil);
|
||||
|
||||
// only update the items that we are coming from if it has not been set
|
||||
|
|
@ -448,15 +452,15 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
[self.completionBlocks addObject:localCompletion];
|
||||
}
|
||||
|
||||
[self _queueUpdateWithCollectionView:collectionView];
|
||||
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
|
||||
}
|
||||
|
||||
- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView
|
||||
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
animated:(BOOL)animated
|
||||
itemUpdates:(void (^)(void))itemUpdates
|
||||
completion:(void (^)(BOOL))completion {
|
||||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(collectionViewBlock != nil);
|
||||
IGParameterAssert(itemUpdates != nil);
|
||||
|
||||
IGListBatchUpdates *batchUpdates = self.batchUpdates;
|
||||
|
|
@ -475,7 +479,7 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
// reset to YES in -cleanupState
|
||||
self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated;
|
||||
|
||||
[self _queueUpdateWithCollectionView:collectionView];
|
||||
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -566,11 +570,11 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)reloadDataWithCollectionView:(UICollectionView *)collectionView
|
||||
- (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock
|
||||
completion:(nullable IGListUpdatingCompletion)completion {
|
||||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(collectionViewBlock != nil);
|
||||
IGParameterAssert(reloadUpdateBlock != nil);
|
||||
|
||||
IGListUpdatingCompletion localCompletion = completion;
|
||||
|
|
@ -580,7 +584,7 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
|
||||
self.reloadUpdates = reloadUpdateBlock;
|
||||
self.queuedReloadData = YES;
|
||||
[self _queueUpdateWithCollectionView:collectionView];
|
||||
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
|
||||
}
|
||||
|
||||
- (void)reloadCollectionView:(UICollectionView *)collectionView sections:(NSIndexSet *)sections {
|
||||
|
|
@ -595,5 +599,6 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
return [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsObjectPersonality];
|
||||
}
|
||||
|
||||
- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView
|
||||
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
fromObjects:(NSArray *)fromObjects
|
||||
toObjectsBlock:(IGListToObjectBlock)toObjectsBlock
|
||||
animated:(BOOL)animated
|
||||
|
|
@ -25,18 +25,18 @@
|
|||
NSArray *toObjects = toObjectsBlock() ?: @[];
|
||||
objectTransitionBlock(toObjects);
|
||||
}
|
||||
[self _synchronousReloadDataWithCollectionView:collectionView];
|
||||
[self _synchronousReloadDataWithCollectionView:collectionViewBlock()];
|
||||
if (completion) {
|
||||
completion(YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView
|
||||
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
animated:(BOOL)animated
|
||||
itemUpdates:(IGListItemUpdateBlock)itemUpdates
|
||||
completion:(IGListUpdatingCompletion)completion {
|
||||
itemUpdates();
|
||||
[self _synchronousReloadDataWithCollectionView:collectionView];
|
||||
[self _synchronousReloadDataWithCollectionView:collectionViewBlock()];
|
||||
if (completion) {
|
||||
completion(YES);
|
||||
}
|
||||
|
|
@ -66,9 +66,9 @@
|
|||
[self _synchronousReloadDataWithCollectionView:collectionView];
|
||||
}
|
||||
|
||||
- (void)reloadDataWithCollectionView:(UICollectionView *)collectionView reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock completion:(IGListUpdatingCompletion)completion {
|
||||
- (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock completion:(IGListUpdatingCompletion)completion {
|
||||
reloadUpdateBlock();
|
||||
[self _synchronousReloadDataWithCollectionView:collectionView];
|
||||
[self _synchronousReloadDataWithCollectionView:collectionViewBlock()];
|
||||
if (completion) {
|
||||
completion(YES);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ typedef void (^IGListReloadUpdateBlock)(void);
|
|||
NS_SWIFT_NAME(ListToObjectBlock)
|
||||
typedef NSArray * _Nullable (^IGListToObjectBlock)(void);
|
||||
|
||||
/// A block that returns a collection view to perform updates on.
|
||||
NS_SWIFT_NAME(ListCollectionViewBlock)
|
||||
typedef UICollectionView * _Nullable (^IGListCollectionViewBlock)(void);
|
||||
|
||||
/**
|
||||
Implement this protocol in order to handle both section and row based update events. Implementation should forward or
|
||||
coalesce these events to a backing store or collection.
|
||||
|
|
@ -63,7 +67,7 @@ NS_SWIFT_NAME(ListUpdatingDelegate)
|
|||
/**
|
||||
Tells the delegate to perform a section transition from an old array of objects to a new one.
|
||||
|
||||
@param collectionView The collection view to perform the transition on.
|
||||
@param collectionViewBlock A block returning the collecion view to perform updates on.
|
||||
@param fromObjects The previous objects in the collection view. Objects must conform to `IGListDiffable`.
|
||||
@param toObjectsBlock A block returning the new objects in the collection view. Objects must conform to `IGListDiffable`.
|
||||
@param animated A flag indicating if the transition should be animated.
|
||||
|
|
@ -77,12 +81,12 @@ NS_SWIFT_NAME(ListUpdatingDelegate)
|
|||
The `objectTransitionBlock` block should be called prior to making any `UICollectionView` updates, passing in the `toObjects`
|
||||
that the updater is applying.
|
||||
*/
|
||||
- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView
|
||||
fromObjects:(nullable NSArray<id <IGListDiffable>> *)fromObjects
|
||||
toObjectsBlock:(nullable IGListToObjectBlock)toObjectsBlock
|
||||
animated:(BOOL)animated
|
||||
objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
|
||||
completion:(nullable IGListUpdatingCompletion)completion;
|
||||
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
fromObjects:(nullable NSArray<id <IGListDiffable>> *)fromObjects
|
||||
toObjectsBlock:(nullable IGListToObjectBlock)toObjectsBlock
|
||||
animated:(BOOL)animated
|
||||
objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
|
||||
completion:(nullable IGListUpdatingCompletion)completion;
|
||||
|
||||
/**
|
||||
Tells the delegate to perform item inserts at the given index paths.
|
||||
|
|
@ -128,7 +132,7 @@ NS_SWIFT_NAME(ListUpdatingDelegate)
|
|||
|
||||
/**
|
||||
Tells the delegate to move a section from and to given indexes.
|
||||
|
||||
|
||||
@param collectionView The collection view on which to perform the transition.
|
||||
@param fromIndex The source index of the section to move.
|
||||
@param toIndex The destination index of the section to move.
|
||||
|
|
@ -136,17 +140,17 @@ NS_SWIFT_NAME(ListUpdatingDelegate)
|
|||
- (void)moveSectionInCollectionView:(UICollectionView *)collectionView
|
||||
fromIndex:(NSInteger)fromIndex
|
||||
toIndex:(NSInteger)toIndex;
|
||||
|
||||
|
||||
/**
|
||||
Completely reload data in the collection.
|
||||
|
||||
@param collectionView The collection view to reload.
|
||||
@param collectionViewBlock A block returning the collecion view to reload.
|
||||
@param reloadUpdateBlock A block that must be called when the adapter reloads the collection view.
|
||||
@param completion A completion block to execute when the reload is finished.
|
||||
*/
|
||||
- (void)reloadDataWithCollectionView:(UICollectionView *)collectionView
|
||||
reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock
|
||||
completion:(nullable IGListUpdatingCompletion)completion;
|
||||
- (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock
|
||||
completion:(nullable IGListUpdatingCompletion)completion;
|
||||
|
||||
/**
|
||||
Completely reload each section in the collection view.
|
||||
|
|
@ -159,16 +163,17 @@ NS_SWIFT_NAME(ListUpdatingDelegate)
|
|||
/**
|
||||
Perform an item update block in the collection view.
|
||||
|
||||
@param collectionView The collection view to update.
|
||||
@param collectionViewBlock A block returning the collecion view to perform updates on.
|
||||
@param animated A flag indicating if the transition should be animated.
|
||||
@param itemUpdates A block containing all of the updates.
|
||||
@param completion A completion block to execute when the update is finished.
|
||||
*/
|
||||
- (void)performUpdateWithCollectionView:(UICollectionView *)collectionView
|
||||
animated:(BOOL)animated
|
||||
itemUpdates:(IGListItemUpdateBlock)itemUpdates
|
||||
completion:(nullable IGListUpdatingCompletion)completion;
|
||||
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
|
||||
animated:(BOOL)animated
|
||||
itemUpdates:(IGListItemUpdateBlock)itemUpdates
|
||||
completion:(nullable IGListUpdatingCompletion)completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
@property (nonatomic, assign) IGListBatchUpdateState state;
|
||||
@property (nonatomic, strong, nullable) IGListBatchUpdateData *applyingUpdateData;
|
||||
|
||||
- (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView;
|
||||
- (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView;
|
||||
- (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock;
|
||||
- (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock;
|
||||
- (void)cleanStateBeforeUpdates;
|
||||
- (BOOL)hasChanges;
|
||||
|
||||
|
|
|
|||
|
|
@ -1832,4 +1832,53 @@
|
|||
IGAssertEqualPoint(finalAttribute.center, attribute.center.x + offset.x ,attribute.center.y + offset.y);
|
||||
}
|
||||
|
||||
- (void)test_whenSwappingCollectionViewsAfterUpdate_thatUpdatePerformedOnTheCorrectCollectionView {
|
||||
// BEGIN: setup of FIRST adapter+dataSource+collectionView
|
||||
IGListAdapter *adapter1 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
|
||||
adapter1.experiments |= IGListExperimentGetCollectionViewAtUpdate;
|
||||
|
||||
UICollectionView *collectionView1 = [[UICollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:[UICollectionViewFlowLayout new]];
|
||||
[self.window addSubview:collectionView1];
|
||||
adapter1.collectionView = collectionView1;
|
||||
|
||||
IGTestDelegateDataSource *dataSource1 = [IGTestDelegateDataSource new];
|
||||
dataSource1.objects = @[
|
||||
genTestObject(@1, @1),
|
||||
genTestObject(@2, @1)
|
||||
];
|
||||
adapter1.dataSource = dataSource1;
|
||||
// END: setup of FIRST adapter+dataSource+collectionView
|
||||
|
||||
// BEGIN: setup of SECOND adapter+dataSource+collectionView
|
||||
IGListAdapter *adapter2 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
|
||||
adapter2.experiments |= IGListExperimentGetCollectionViewAtUpdate;
|
||||
|
||||
UICollectionView *collectionView2 = [[UICollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:[UICollectionViewFlowLayout new]];
|
||||
[self.window addSubview:collectionView2];
|
||||
adapter2.collectionView = collectionView2;
|
||||
|
||||
IGTestDelegateDataSource *dataSource2 = [IGTestDelegateDataSource new];
|
||||
dataSource2.objects = @[
|
||||
genTestObject(@3, @1)
|
||||
];
|
||||
adapter2.dataSource = dataSource2;
|
||||
// END: setup of SECOND adapter+dataSource+collectionView
|
||||
|
||||
// delete the last-most section from the FIRST dataSource
|
||||
dataSource1.objects = @[
|
||||
genTestObject(@1, @1)
|
||||
];
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[adapter1 performUpdatesAnimated:YES completion:^(BOOL finished) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
// simulate a collectionView swap (e.g. cell reuse) immediately after an async update is queued
|
||||
adapter1.collectionView = collectionView2;
|
||||
adapter2.collectionView = collectionView1;
|
||||
|
||||
[self waitForExpectationsWithTimeout:30 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
|
||||
@implementation IGListAdapterUpdaterTests
|
||||
|
||||
- (IGListCollectionViewBlock)collectionViewBlock {
|
||||
return ^UICollectionView *{ return self.collectionView; };
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
|
|
@ -56,27 +60,27 @@
|
|||
}
|
||||
|
||||
- (void)test_whenUpdatingWithNil_thatUpdaterHasNoChanges {
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjectsBlock:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:nil toObjectsBlock:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
XCTAssertFalse([self.updater hasChanges]);
|
||||
}
|
||||
|
||||
- (void)test_whenUpdatingtoObjects_thatUpdaterHasChanges {
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
XCTAssertTrue([self.updater hasChanges]);
|
||||
}
|
||||
|
||||
- (void)test_whenUpdatingfromObjects_thatUpdaterHasChanges {
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjectsBlock:^NSArray *{return nil;} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:@[@0] toObjectsBlock:^NSArray *{return nil;} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
XCTAssertTrue([self.updater hasChanges]);
|
||||
}
|
||||
|
||||
- (void)test_whenUpdatingtoObjects_withfromObjects_thatUpdaterHasChanges {
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjectsBlock:^NSArray *{return @[@1];} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:@[@0] toObjectsBlock:^NSArray *{return @[@1];} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
XCTAssertTrue([self.updater hasChanges]);
|
||||
}
|
||||
|
||||
- (void)test_whenCleaningUpState_withChanges_thatUpdaterHasNoChanges {
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil];
|
||||
XCTAssertTrue([self.updater hasChanges]);
|
||||
[self.updater cleanStateBeforeUpdates];
|
||||
XCTAssertFalse([self.updater hasChanges]);
|
||||
|
|
@ -84,10 +88,10 @@
|
|||
|
||||
- (void)test_whenReloadingData_thatCollectionViewUpdates {
|
||||
self.dataSource.sections = @[[IGSectionObject sectionWithObjects:@[]]];
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 1);
|
||||
self.dataSource.sections = @[];
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 0);
|
||||
}
|
||||
|
||||
|
|
@ -103,11 +107,11 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 1);
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 2);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
|
@ -126,11 +130,11 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 2);
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 1);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
|
@ -149,12 +153,12 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 1);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 2);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2);
|
||||
|
|
@ -177,13 +181,13 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 2);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 3);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
|
||||
|
|
@ -198,7 +202,7 @@
|
|||
[IGSectionObject sectionWithObjects:@[@0, @1]],
|
||||
[IGSectionObject sectionWithObjects:@[@0, @1]]
|
||||
];
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 2);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2);
|
||||
|
|
@ -226,7 +230,7 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
|
||||
// the collection view has been setup with 1 section and now needs layout
|
||||
// calling performBatchUpdates: on a collection view needing layout will force layout
|
||||
|
|
@ -234,7 +238,7 @@
|
|||
[self.collectionView setNeedsLayout];
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 1);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
|
@ -253,7 +257,7 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
|
||||
__block NSInteger completionCounter = 0;
|
||||
|
||||
|
|
@ -266,7 +270,7 @@
|
|||
[IGSectionObject sectionWithObjects:@[]]
|
||||
];
|
||||
};
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:anotherTo animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:anotherTo animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
completionCounter++;
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 3);
|
||||
XCTAssertEqual(completionCounter, 2);
|
||||
|
|
@ -275,7 +279,7 @@
|
|||
};
|
||||
|
||||
XCTestExpectation *expectation2 = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:^(NSArray *toObjects) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:^(NSArray *toObjects) {
|
||||
// executing this block within the updater is just before performBatchUpdates: are applied
|
||||
// should be able to queue another update here, similar to an update being queued between it beginning and executing
|
||||
// the performBatchUpdates: block
|
||||
|
|
@ -292,13 +296,13 @@
|
|||
}
|
||||
|
||||
- (void)test_whenQueuingItemUpdates_thatUpdaterHasChanges {
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{} completion:nil];
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{} completion:nil];
|
||||
XCTAssertTrue([self.updater hasChanges]);
|
||||
}
|
||||
|
||||
- (void)test_whenOnlyQueueingItemUpdates_thatUpdateBlockExecutes {
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{
|
||||
// expectation should be triggered. test failure is a timeout
|
||||
[expectation fulfill];
|
||||
} completion:nil];
|
||||
|
|
@ -309,7 +313,7 @@
|
|||
__block BOOL itemUpdateBlockExecuted = NO;
|
||||
__block BOOL sectionUpdateBlockExecuted = NO;
|
||||
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock]
|
||||
fromObjects:nil
|
||||
toObjectsBlock:^NSArray *{return @[[IGSectionObject sectionWithObjects:@[@1]]];}
|
||||
animated:YES objectTransitionBlock:^(NSArray * toObjects) {
|
||||
|
|
@ -319,7 +323,7 @@
|
|||
completion:nil];
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{
|
||||
itemUpdateBlockExecuted = YES;
|
||||
} completion:^(BOOL finished) {
|
||||
// test in the item completion block that the SECTION operations have been performed
|
||||
|
|
@ -355,13 +359,13 @@
|
|||
};
|
||||
|
||||
self.dataSource.sections = from;
|
||||
[self.updater performReloadDataWithCollectionView:self.collectionView];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]];
|
||||
|
||||
// without moves as inserts, we would assert b/c the # of items in each section changes
|
||||
self.updater.movesAsDeletesInserts = YES;
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
XCTAssertEqual([self.collectionView numberOfSections], 3);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2);
|
||||
|
|
@ -422,7 +426,7 @@
|
|||
id compilerFriendlyNil = nil;
|
||||
[[mockDelegate reject] listAdapterUpdater:self.updater willReloadDataWithCollectionView:compilerFriendlyNil];
|
||||
[[mockDelegate reject] listAdapterUpdater:self.updater didReloadDataWithCollectionView:compilerFriendlyNil];
|
||||
[self.updater performReloadDataWithCollectionView:compilerFriendlyNil];
|
||||
[self.updater performReloadDataWithCollectionViewBlock:^UICollectionView *{ return compilerFriendlyNil; }];
|
||||
[mockDelegate verify];
|
||||
}
|
||||
|
||||
|
|
@ -432,7 +436,7 @@
|
|||
id compilerFriendlyNil = nil;
|
||||
[[mockDelegate reject] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:compilerFriendlyNil];
|
||||
[[mockDelegate reject] listAdapterUpdater:self.updater didPerformBatchUpdates:[OCMArg any] collectionView:compilerFriendlyNil];
|
||||
[self.updater performBatchUpdatesWithCollectionView:compilerFriendlyNil];
|
||||
[self.updater performBatchUpdatesWithCollectionViewBlock:^UICollectionView *{ return compilerFriendlyNil; }];
|
||||
[mockDelegate verify];
|
||||
}
|
||||
|
||||
|
|
@ -461,7 +465,7 @@
|
|||
];
|
||||
|
||||
IGListAdapterUpdater *updater = [IGListAdapterUpdater new];
|
||||
[updater performReloadDataWithCollectionView:collectionView];
|
||||
[updater performReloadDataWithCollectionViewBlock:^UICollectionView *{ return collectionView; }];
|
||||
|
||||
XCTAssertEqual([collectionView numberOfSections], 1);
|
||||
XCTAssertEqual([collectionView numberOfItemsInSection:0], 1);
|
||||
|
|
@ -470,7 +474,7 @@
|
|||
[IGSectionObject sectionWithObjects:@[@1]],
|
||||
[IGSectionObject sectionWithObjects:@[@1, @2, @3, @4]]
|
||||
];
|
||||
[updater performReloadDataWithCollectionView:collectionView];
|
||||
[updater performReloadDataWithCollectionViewBlock:^UICollectionView *{ return collectionView; }];
|
||||
|
||||
XCTAssertEqual([collectionView numberOfSections], 2);
|
||||
XCTAssertEqual([collectionView numberOfItemsInSection:0], 1);
|
||||
|
|
@ -493,7 +497,7 @@
|
|||
[IGSectionObject sectionWithObjects:@[]]
|
||||
];
|
||||
};
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
waitExpectation;
|
||||
|
|
@ -520,7 +524,7 @@
|
|||
[IGSectionObject sectionWithObjects:@[]]
|
||||
];
|
||||
};
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
waitExpectation;
|
||||
|
|
@ -532,12 +536,12 @@
|
|||
self.dataSource.sections = @[object];
|
||||
|
||||
__block BOOL reloadDataCompletionExecuted = NO;
|
||||
[self.updater reloadDataWithCollectionView:self.collectionView reloadUpdateBlock:^{} completion:^(BOOL finished) {
|
||||
[self.updater reloadDataWithCollectionViewBlock:[self collectionViewBlock] reloadUpdateBlock:^{} completion:^(BOOL finished) {
|
||||
reloadDataCompletionExecuted = YES;
|
||||
}];
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{
|
||||
object.objects = @[@2, @1, @4, @5];
|
||||
[self.updater insertItemsIntoCollectionView:self.collectionView indexPaths:@[
|
||||
[NSIndexPath indexPathForItem:2 inSection:0],
|
||||
|
|
@ -565,7 +569,7 @@
|
|||
|
||||
__block BOOL objectTransitionBlockExecuted = NO;
|
||||
__block BOOL completionBlockExecuted = NO;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock]
|
||||
fromObjects:self.dataSource.sections
|
||||
toObjectsBlock:^NSArray *{return self.dataSource.sections;}
|
||||
animated:YES
|
||||
|
|
@ -577,7 +581,7 @@
|
|||
}];
|
||||
|
||||
XCTestExpectation *expectation = genExpectation;
|
||||
[self.updater performUpdateWithCollectionView:self.collectionView animated:YES itemUpdates:^{
|
||||
[self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{
|
||||
object.objects = @[@2, @1, @4, @5];
|
||||
[self.updater insertItemsIntoCollectionView:self.collectionView indexPaths:@[
|
||||
[NSIndexPath indexPathForItem:2 inSection:0],
|
||||
|
|
|
|||
Loading…
Reference in a new issue