Files
nirvana-ios/Pods/FirebaseFirestoreUI/FirebaseFirestoreUI/Sources/FUIFirestoreCollectionViewDataSource.m
T

205 lines
7.8 KiB
Objective-C

// clang-format off
//
// Copyright (c) 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// clang-format on
#import "FirebaseFirestoreUI/Sources/Public/FirebaseFirestoreUI/FUIFirestoreCollectionViewDataSource.h"
@interface FUIFirestoreCollectionViewDataSource () <FUIBatchedArrayDelegate>
@property (nonatomic, readonly, nonnull) FUIBatchedArray *collection;
// We need to maintain our own count property because the self.collection.count is sometimes
// inconsistent with how the -[UICollectionView performBatchUpdates:] method expects it to work.
// -[UICollectionView performBatchUpdates:] calls -[UICollectionViewDataSource count] at the
// beginning and at the end of the method, and if the value of -[UICollectionViewDataSource count]
// after -[UICollectionView performBatchUpdates:] is not equal to the value of
// -[UICollectionViewDataSource count] before + (rows added - rows deleted) it throws an
// NSInternalInconsistencyException.
@property (nonatomic) NSUInteger count;
/**
* The callback to populate a subclass of UICollectionViewCell with an object
* provided by the datasource.
*/
@property (copy, nonatomic, readonly) UICollectionViewCell *(^populateCellAtIndexPath)
(UICollectionView *collectionView, NSIndexPath *indexPath, FIRDocumentSnapshot *object);
@end
@implementation FUIFirestoreCollectionViewDataSource
#pragma mark - FUIDataSource initializer methods
- (instancetype)initWithCollection:(FUIBatchedArray *)collection
populateCell:(UICollectionViewCell * (^)(UICollectionView *,
NSIndexPath *,
FIRDocumentSnapshot *))populateCell {
self = [super init];
if (self) {
_collection = collection;
_collection.delegate = self;
_populateCellAtIndexPath = populateCell;
_count = collection.count;
}
return self;
}
- (instancetype)initWithQuery:(FIRQuery *)query
populateCell:(UICollectionViewCell *(^)(UICollectionView *,
NSIndexPath *,
FIRDocumentSnapshot *))populateCell {
FUIBatchedArray *array = [[FUIBatchedArray alloc] initWithQuery:query delegate:self];
return [self initWithCollection:array populateCell:populateCell];
}
- (NSArray<FIRDocumentSnapshot *> *)items {
return self.collection.items;
}
- (FIRDocumentSnapshot *)snapshotAtIndex:(NSInteger)index {
return self.collection[index];
}
- (void)bindToView:(UICollectionView *)view {
self.collectionView = view;
view.dataSource = self;
[self.collection observeQuery];
}
- (void)unbind {
self.collectionView.dataSource = nil;
self.collectionView = nil;
[self.collection stopObserving];
}
- (FIRQuery *)query {
return self.collection.query;
}
- (void)setQuery:(FIRQuery *)query {
self.collection.query = query;
}
#pragma mark - FUIBatchedArrayDelegate methods
- (void)batchedArray:(FUIBatchedArray *)array
willUpdateWithDiff:(FUISnapshotArrayDiff<FIRDocumentSnapshot *> *)diff {
// This fixes an issue where UICollectionView crashes due to an invalid number of items
// https://fangpenlin.com/posts/2016/04/29/uicollectionview-invalid-number-of-items-crash-issue/
[self.collectionView numberOfItemsInSection:0];
}
- (void)batchedArray:(FUIBatchedArray *)array
didUpdateWithDiff:(FUISnapshotArrayDiff<FIRDocumentSnapshot *> *)diff {
[self.collectionView performBatchUpdates:^{
NSMutableArray *deletedIndexPaths =
[NSMutableArray arrayWithCapacity:diff.deletedIndexes.count];
for (NSNumber *deletedIndex in diff.deletedIndexes) {
NSIndexPath *deleted = [NSIndexPath indexPathForItem:deletedIndex.integerValue inSection:0];
[deletedIndexPaths addObject:deleted];
}
[self.collectionView deleteItemsAtIndexPaths:deletedIndexPaths];
NSMutableArray *changedIndexPaths =
[NSMutableArray arrayWithCapacity:diff.changedIndexes.count];
for (NSNumber *changedIndex in diff.changedIndexes) {
NSIndexPath *changed = [NSIndexPath indexPathForItem:changedIndex.integerValue inSection:0];
[changedIndexPaths addObject:changed];
}
// Use a delete and insert instead of a reload. See
// https://stackoverflow.com/questions/42147822/uicollectionview-batchupdate-edge-case-fails
[self.collectionView deleteItemsAtIndexPaths:changedIndexPaths];
[self.collectionView insertItemsAtIndexPaths:changedIndexPaths];
for (NSInteger i = 0; i < diff.movedInitialIndexes.count; i++) {
NSInteger initialIndex = diff.movedInitialIndexes[i].integerValue;
NSInteger finalIndex = diff.movedResultIndexes[i].integerValue;
NSIndexPath *initialPath = [NSIndexPath indexPathForItem:initialIndex inSection:0];
NSIndexPath *finalPath = [NSIndexPath indexPathForItem:finalIndex inSection:0];
[self.collectionView moveItemAtIndexPath:initialPath toIndexPath:finalPath];
}
NSMutableArray *insertedIndexPaths =
[NSMutableArray arrayWithCapacity:diff.insertedIndexes.count];
for (NSNumber *insertedIndex in diff.insertedIndexes) {
NSIndexPath *inserted = [NSIndexPath indexPathForItem:insertedIndex.integerValue inSection:0];
[insertedIndexPaths addObject:inserted];
}
[self.collectionView insertItemsAtIndexPaths:insertedIndexPaths];
self.count = self.collection.count;
} completion:^(BOOL finished) {
// Reload paths that have been moved.
NSMutableArray *movedIndexPaths =
[NSMutableArray arrayWithCapacity:diff.movedResultIndexes.count];
for (NSNumber *movedResultIndex in diff.movedResultIndexes) {
NSIndexPath *moved = [NSIndexPath indexPathForItem:movedResultIndex.integerValue inSection:0];
[movedIndexPaths addObject:moved];
}
[self.collectionView reloadItemsAtIndexPaths:movedIndexPaths];
}];
}
- (void)batchedArray:(FUIBatchedArray *)array queryDidFailWithError:(NSError *)error {
if (self.queryErrorHandler != nil) {
self.queryErrorHandler(error);
} else {
NSLog(@"%@ Unhandled Firestore error: %@. Set the queryErrorHandler property to debug.",
self, error);
}
}
#pragma mark - UICollectionViewDataSource methods
- (nonnull UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView
cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
FIRDocumentSnapshot *snap = [self.collection.items objectAtIndex:indexPath.item];
UICollectionViewCell *cell = self.populateCellAtIndexPath(collectionView, indexPath, snap);
return cell;
}
- (NSInteger)numberOfSectionsInCollectionView:(nonnull UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return self.count;
}
@end
@implementation UICollectionView (FUIFirestoreCollectionViewDataSource)
- (FUIFirestoreCollectionViewDataSource *)bindToFirestoreQuery:(FIRQuery *)query
populateCell:(UICollectionViewCell *(^)(UICollectionView *,
NSIndexPath *,
FIRDocumentSnapshot *))populateCell {
FUIFirestoreCollectionViewDataSource *dataSource =
[[FUIFirestoreCollectionViewDataSource alloc] initWithQuery:query populateCell:populateCell];
[dataSource bindToView:self];
return dataSource;
}
@end