adding pods method of package managing
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import "FirebaseFirestoreUI/Sources/Public/FirebaseFirestoreUI/FUIBatchedArray.h"
|
||||
|
||||
@interface FUIBatchedArray ()
|
||||
|
||||
@property (nonatomic, readwrite, copy) NSArray<FIRDocumentSnapshot *> *items;
|
||||
@property (nonatomic, readwrite) id<FIRListenerRegistration> observer;
|
||||
|
||||
/// A private member used to keep track of whether or not the current array
|
||||
/// contents are in sync with the query. If the query changes we cannot use
|
||||
/// the FIRDocumentChanges provided with the next update to produce a diff,
|
||||
/// so we need to keep track of it somehow.
|
||||
@property (nonatomic, readwrite) BOOL isInSync;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FUIBatchedArray
|
||||
|
||||
- (instancetype)initWithQuery:(FIRQuery *)query delegate:(id<FUIBatchedArrayDelegate>)delegate {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_delegate = delegate;
|
||||
_query = query;
|
||||
_items = @[];
|
||||
|
||||
// Firestore sends initial data as insertions, so this can be YES on init.
|
||||
_isInSync = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)observeQuery {
|
||||
if (self.observer != nil) { return; }
|
||||
// Since self retains the query, the query's block shouldn't retain self.
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
self.observer = [self.query addSnapshotListener:^(FIRQuerySnapshot *snapshot, NSError *error) {
|
||||
__strong typeof(weakSelf) sself = weakSelf;
|
||||
if (sself == nil) { return; }
|
||||
if (error != nil) {
|
||||
NSLog(@"Firestore error: %@", error);
|
||||
|
||||
if ([sself.delegate respondsToSelector:@selector(batchedArray:queryDidFailWithError:)]) {
|
||||
[sself.delegate batchedArray:sself queryDidFailWithError:error];
|
||||
}
|
||||
}
|
||||
|
||||
FUISnapshotArrayDiff *diff;
|
||||
|
||||
if (sself.isInSync) {
|
||||
diff = [[FUISnapshotArrayDiff alloc] initWithInitialArray:sself.items
|
||||
resultArray:snapshot.documents
|
||||
documentChanges:snapshot.documentChanges];
|
||||
} else {
|
||||
diff = [[FUISnapshotArrayDiff alloc] initWithInitialArray:sself.items
|
||||
resultArray:snapshot.documents];
|
||||
}
|
||||
|
||||
if ([sself.delegate respondsToSelector:@selector(batchedArray:willUpdateWithDiff:)]) {
|
||||
[sself.delegate batchedArray:sself willUpdateWithDiff:diff];
|
||||
}
|
||||
|
||||
sself.items = snapshot.documents;
|
||||
sself.isInSync = YES;
|
||||
|
||||
if ([sself.delegate respondsToSelector:@selector(batchedArray:didUpdateWithDiff:)]) {
|
||||
[sself.delegate batchedArray:sself didUpdateWithDiff:diff];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopObserving {
|
||||
if (self.observer == nil) { return; }
|
||||
[self.observer remove];
|
||||
self.observer = nil;
|
||||
self.isInSync = NO;
|
||||
}
|
||||
|
||||
- (void)setQuery:(FIRQuery *)query {
|
||||
self.isInSync = NO;
|
||||
BOOL wasObserving = self.observer != nil;
|
||||
[self stopObserving];
|
||||
_query = query;
|
||||
if (wasObserving) {
|
||||
[self observeQuery];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)count {
|
||||
return self.items.count;
|
||||
}
|
||||
|
||||
- (FIRDocumentSnapshot *)objectAtIndex:(NSInteger)index {
|
||||
return self.items[index];
|
||||
}
|
||||
|
||||
- (FIRDocumentSnapshot *)objectAtIndexedSubscript:(NSInteger)index {
|
||||
return [self objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopObserving];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface FIRDocumentSnapshot (FirebaseUI) <NSCopying>
|
||||
@end
|
||||
|
||||
@implementation FIRDocumentSnapshot (FirebaseUI)
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@: %p, id: %@>",
|
||||
NSStringFromClass([self class]), self, self.documentID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface FIRDocumentChange (FirebaseUI)
|
||||
@end
|
||||
|
||||
@implementation FIRDocumentChange (FirebaseUI)
|
||||
|
||||
- (NSString *)description {
|
||||
NSString *changeType;
|
||||
switch (self.type) {
|
||||
case FIRDocumentChangeTypeAdded:
|
||||
changeType = @"Add";
|
||||
break;
|
||||
case FIRDocumentChangeTypeRemoved:
|
||||
changeType = @"Delete";
|
||||
break;
|
||||
case FIRDocumentChangeTypeModified:
|
||||
changeType = @"Change";
|
||||
break;
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"<%@: %p, %@: %lu -> %lu>",
|
||||
NSStringFromClass([self class]), self, changeType,
|
||||
(unsigned long)self.oldIndex, (unsigned long)self.newIndex];
|
||||
}
|
||||
|
||||
@end
|
||||
Generated
+204
@@ -0,0 +1,204 @@
|
||||
// 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
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// 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/FUIFirestoreTableViewDataSource.h"
|
||||
|
||||
@interface FUIFirestoreTableViewDataSource () <FUIBatchedArrayDelegate>
|
||||
|
||||
@property (strong, nonatomic, readwrite) UITableViewCell *(^populateCell)
|
||||
(UITableView *tableView, NSIndexPath *indexPath, FIRDocumentSnapshot *snap);
|
||||
|
||||
@property (strong, nonatomic, readonly) FUIBatchedArray *collection;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FUIFirestoreTableViewDataSource
|
||||
|
||||
#pragma mark - FUIDataSource initializer methods
|
||||
|
||||
- (instancetype)initWithCollection:(FUIBatchedArray *)collection
|
||||
populateCell:(UITableViewCell *(^)(UITableView *,
|
||||
NSIndexPath *,
|
||||
FIRDocumentSnapshot *))populateCell {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_collection = collection;
|
||||
_collection.delegate = self;
|
||||
_populateCell = populateCell;
|
||||
_animation = UITableViewRowAnimationAutomatic;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithQuery:(FIRQuery *)query
|
||||
populateCell:(UITableViewCell *(^)(UITableView *,
|
||||
NSIndexPath *,
|
||||
FIRDocumentSnapshot *))populateCell {
|
||||
FUIBatchedArray *array = [[FUIBatchedArray alloc] initWithQuery:query delegate:self];
|
||||
return [self initWithCollection:array populateCell:populateCell];
|
||||
}
|
||||
|
||||
- (NSUInteger)count {
|
||||
return self.collection.count;
|
||||
}
|
||||
|
||||
- (NSArray<FIRDocumentSnapshot *> *)items {
|
||||
return self.collection.items;
|
||||
}
|
||||
|
||||
- (FIRDocumentSnapshot *)snapshotAtIndex:(NSInteger)index {
|
||||
return [self.collection objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (void)bindToView:(UITableView *)view {
|
||||
self.tableView = view;
|
||||
view.dataSource = self;
|
||||
[self.collection observeQuery];
|
||||
}
|
||||
|
||||
- (void)unbind {
|
||||
self.tableView.dataSource = nil;
|
||||
self.tableView = 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
|
||||
didUpdateWithDiff:(FUISnapshotArrayDiff<FIRDocumentSnapshot *> *)diff {
|
||||
[self.tableView beginUpdates];
|
||||
|
||||
NSMutableArray *deletedIndexPaths =
|
||||
[NSMutableArray arrayWithCapacity:diff.deletedIndexes.count];
|
||||
for (NSNumber *deletedIndex in diff.deletedIndexes) {
|
||||
NSIndexPath *deleted = [NSIndexPath indexPathForRow:deletedIndex.integerValue inSection:0];
|
||||
[deletedIndexPaths addObject:deleted];
|
||||
}
|
||||
[self.tableView deleteRowsAtIndexPaths:deletedIndexPaths
|
||||
withRowAnimation:self.animation];
|
||||
|
||||
NSMutableArray *changedIndexPaths =
|
||||
[NSMutableArray arrayWithCapacity:diff.changedIndexes.count];
|
||||
for (NSNumber *changedIndex in diff.changedIndexes) {
|
||||
NSIndexPath *changed = [NSIndexPath indexPathForRow:changedIndex.integerValue inSection:0];
|
||||
[changedIndexPaths addObject:changed];
|
||||
}
|
||||
[self.tableView reloadRowsAtIndexPaths:changedIndexPaths
|
||||
withRowAnimation:self.animation];
|
||||
|
||||
for (NSInteger i = 0; i < diff.movedInitialIndexes.count; i++) {
|
||||
NSInteger initialIndex = diff.movedInitialIndexes[i].integerValue;
|
||||
NSInteger finalIndex = diff.movedResultIndexes[i].integerValue;
|
||||
NSIndexPath *initialPath = [NSIndexPath indexPathForRow:initialIndex inSection:0];
|
||||
NSIndexPath *finalPath = [NSIndexPath indexPathForRow:finalIndex inSection:0];
|
||||
|
||||
[self.tableView moveRowAtIndexPath:initialPath toIndexPath:finalPath];
|
||||
}
|
||||
|
||||
NSMutableArray *insertedIndexPaths =
|
||||
[NSMutableArray arrayWithCapacity:diff.insertedIndexes.count];
|
||||
for (NSNumber *insertedIndex in diff.insertedIndexes) {
|
||||
NSIndexPath *inserted = [NSIndexPath indexPathForRow:insertedIndex.integerValue inSection:0];
|
||||
[insertedIndexPaths addObject:inserted];
|
||||
}
|
||||
[self.tableView insertRowsAtIndexPaths:insertedIndexPaths
|
||||
withRowAnimation:self.animation];
|
||||
|
||||
[self.tableView endUpdates];
|
||||
|
||||
// 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.tableView reloadRowsAtIndexPaths:movedIndexPaths
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
|
||||
- (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);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)batchedArray:(nonnull FUIBatchedArray *)array
|
||||
willUpdateWithDiff:(nonnull FUISnapshotArrayDiff<FIRDocumentSnapshot *> *)diff {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UITableViewDataSource methods
|
||||
|
||||
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
FIRDocumentSnapshot *snap = [self.collection.items objectAtIndex:indexPath.row];
|
||||
UITableViewCell *cell = self.populateCell(tableView, indexPath, snap);
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.collection.count;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UITableView (FUIFirestoreTableViewDataSource)
|
||||
|
||||
- (FUIFirestoreTableViewDataSource *)bindToFirestoreQuery:(FIRQuery *)query
|
||||
populateCell:(UITableViewCell *(^)(UITableView *tableView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *snap))populateCell {
|
||||
FUIFirestoreTableViewDataSource *dataSource =
|
||||
[[FUIFirestoreTableViewDataSource alloc] initWithQuery:query populateCell:populateCell];
|
||||
[dataSource bindToView:self];
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@end
|
||||
+524
@@ -0,0 +1,524 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import <FirebaseFirestore/FirebaseFirestore.h>
|
||||
|
||||
#import "FirebaseFirestoreUI/Sources/Public/FirebaseFirestoreUI/FUISnapshotArrayDiff.h"
|
||||
|
||||
@interface FUIArraySlice ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation FUIArraySlice
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithArray:@[] startIndex:0 length:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array startIndex:(NSInteger)start length:(NSInteger)length {
|
||||
NSParameterAssert(start >= 0);
|
||||
NSParameterAssert(length >= 0);
|
||||
NSParameterAssert(start + length <= array.count);
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_startIndex = start;
|
||||
_count = length;
|
||||
_backingArray = [array copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array startIndex:(NSInteger)start endIndex:(NSInteger)end {
|
||||
NSInteger length = end - start;
|
||||
NSAssert(length >= 0, @"Cannot create array slice with length less than zero");
|
||||
return [self initWithArray:array startIndex:start length:length];
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array {
|
||||
return [self initWithArray:array startIndex:0 length:array.count];
|
||||
}
|
||||
|
||||
- (id)objectAtIndex:(NSInteger)index {
|
||||
NSAssert(index >= self.startIndex && index < self.endIndex, @"Index out of bounds");
|
||||
return self.backingArray[index];
|
||||
}
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSInteger)index {
|
||||
return [self objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (FUIArraySlice *)suffixFromIndex:(NSInteger)index {
|
||||
return [[FUIArraySlice alloc] initWithArray:self.backingArray
|
||||
startIndex:index
|
||||
endIndex:self.endIndex];
|
||||
}
|
||||
|
||||
- (NSInteger)endIndex {
|
||||
return self.startIndex + self.count;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
NSUInteger hash = 5381;
|
||||
for (NSInteger i = self.startIndex; i < self.endIndex; i++) {
|
||||
NSUInteger intermediate = [self.backingArray[i] hash];
|
||||
hash = (hash << 5) + hash + intermediate;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(FUIArraySlice *)object {
|
||||
if (![object isKindOfClass:[self class]]) { return NO; }
|
||||
if (object.count != self.count) { return NO; }
|
||||
if (object.startIndex != self.startIndex) { return NO; }
|
||||
|
||||
for (NSInteger i = self.startIndex; i < self.endIndex; i++) {
|
||||
if (![[object objectAtIndex:i] isEqual:[self objectAtIndex:i]]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FUIUnorderedPair ()
|
||||
|
||||
@property (nonatomic, readwrite) id left;
|
||||
@property (nonatomic, readwrite) id right;
|
||||
|
||||
- (instancetype)initWithLeft:(id)left right:(id)right NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FUIUnorderedPair
|
||||
|
||||
- (instancetype)init {
|
||||
abort();
|
||||
}
|
||||
|
||||
- (instancetype)initWithLeft:(id)left right:(id)right {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_left = left;
|
||||
_right = right;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [self.left hash] ^ [self.right hash];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(FUIUnorderedPair *)object {
|
||||
if (![object isKindOfClass:[self class]]) { return NO; }
|
||||
return ([self.left isEqual:object.left] && [self.right isEqual:object.right]) ||
|
||||
([self.right isEqual:object.left] && [self.left isEqual:object.right]);
|
||||
}
|
||||
|
||||
// This class is immutable, so copies just return self.
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copy {
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
FUIUnorderedPair *FUIUnorderedPairMake(id left, id right) {
|
||||
FUIUnorderedPair *pair = [[FUIUnorderedPair alloc] initWithLeft:left right:right];
|
||||
return pair;
|
||||
}
|
||||
|
||||
@interface FUILCS ()
|
||||
@property (nonatomic, readonly) NSMutableDictionary<FUIUnorderedPair<FUIArraySlice<id> *> *, NSMutableArray<id> *> *memo;
|
||||
@end
|
||||
|
||||
@implementation FUILCS
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_memo = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)lcsWithInitial:(FUIArraySlice *)lhs result:(FUIArraySlice *)rhs {
|
||||
if (lhs.count == 0 && rhs.count == 0) {
|
||||
return [NSMutableArray array];
|
||||
}
|
||||
|
||||
FUIUnorderedPair *args = FUIUnorderedPairMake(lhs, rhs);
|
||||
NSMutableArray *memoized = _memo[args];
|
||||
if (memoized != nil) {
|
||||
return memoized;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
FUIArraySlice *shorter;
|
||||
FUIArraySlice *longer;
|
||||
if (lhs.count <= rhs.count) {
|
||||
shorter = lhs; longer = rhs;
|
||||
} else {
|
||||
shorter = rhs; longer = lhs;
|
||||
}
|
||||
|
||||
NSMutableArray *aggregate = [NSMutableArray arrayWithCapacity:shorter.count];
|
||||
|
||||
// Aggregate common elements.
|
||||
NSInteger shortOffset = shorter.startIndex;
|
||||
NSInteger longOffset = longer.startIndex;
|
||||
for (NSInteger i = 0; i < shorter.count; i++) {
|
||||
if ([shorter[i + shortOffset] isEqual:longer[i + longOffset]]) {
|
||||
[aggregate addObject:shorter[i + shortOffset]];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// LCS is the entire shorter collection.
|
||||
if (aggregate.count == shorter.count) {
|
||||
_memo[args] = aggregate;
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
// Reached uncommon element, so try LCS of both sides minus the uncommon element or any
|
||||
// previously aggregated common elements.
|
||||
NSMutableArray *right =
|
||||
[self lcsWithInitial:[shorter suffixFromIndex:shortOffset + aggregate.count]
|
||||
result:[longer suffixFromIndex:longOffset + aggregate.count + 1]];
|
||||
|
||||
// Exit early, avoiding one recurse
|
||||
if (right.count == shorter.count) {
|
||||
[aggregate addObjectsFromArray:right];
|
||||
_memo[args] = aggregate;
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
NSMutableArray *left =
|
||||
[self lcsWithInitial:[shorter suffixFromIndex:shortOffset + aggregate.count + 1]
|
||||
result:[longer suffixFromIndex:longOffset + aggregate.count]];
|
||||
|
||||
// Return the aggregate plus the greater of the two subsequences.
|
||||
if (left.count > right.count) {
|
||||
[aggregate addObjectsFromArray:left];
|
||||
} else {
|
||||
[aggregate addObjectsFromArray:right];
|
||||
}
|
||||
|
||||
_memo[args] = aggregate;
|
||||
return aggregate;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSArray *)lcsWithInitialArray:(NSArray *)initial resultArray:(NSArray *)result {
|
||||
FUILCS *lcs = [[FUILCS alloc] init];
|
||||
FUIArraySlice *left = [[FUIArraySlice alloc] initWithArray:initial];
|
||||
FUIArraySlice *right = [[FUIArraySlice alloc] initWithArray:result];
|
||||
return [[lcs lcsWithInitial:left result:right] copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FUISnapshotArrayDiff
|
||||
|
||||
- (instancetype)initWithInitialArray:(NSArray *)initialArray resultArray:(NSArray *)resultArray {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_initial = [initialArray copy];
|
||||
_result = [resultArray copy];
|
||||
[self buildDiffs];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)buildDiffs {
|
||||
NSArray *lcs = [FUILCS lcsWithInitialArray:_initial resultArray:_result];
|
||||
|
||||
// A map of deleted elements and their indexes, which will be used later to convert
|
||||
// deletes into moves. These must be arrays since objects may not be unique.
|
||||
NSMutableDictionary<id, NSMutableSet<NSNumber *> *> *deleted =
|
||||
[NSMutableDictionary dictionaryWithCapacity:_initial.count];
|
||||
|
||||
NSMutableArray<NSNumber *> *deletedIndexes = [NSMutableArray arrayWithCapacity:_initial.count];
|
||||
NSMutableArray *deletedObjects = [NSMutableArray arrayWithCapacity:_initial.count];
|
||||
|
||||
NSMutableArray<NSNumber *> *insertedIndexes = [NSMutableArray arrayWithCapacity:_result.count];
|
||||
NSMutableArray *insertedObjects = [NSMutableArray arrayWithCapacity:_result.count];
|
||||
|
||||
NSMutableArray<NSNumber *> *changedIndexes = [NSMutableArray arrayWithCapacity:_initial.count];
|
||||
NSMutableArray *changedObjects = [NSMutableArray arrayWithCapacity:_initial.count];
|
||||
|
||||
NSMutableArray<NSNumber *> *movedInitialIndexes = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *movedResultIndexes = [NSMutableArray array];
|
||||
NSMutableArray *movedObjects = [NSMutableArray array];
|
||||
|
||||
// Build the array of deleted items by examining the initial array and LCS.
|
||||
// All deleted items and their indexes go into the dictionary of deleted stuff,
|
||||
// so we can tell later on which ones should be moves and which ones should be deletes.
|
||||
NSInteger lcsIndex = 0;
|
||||
for (NSInteger i = 0; i < _initial.count; i++) {
|
||||
id object = _initial[i];
|
||||
id lcsObject;
|
||||
if (lcsIndex < lcs.count) { lcsObject = lcs[lcsIndex]; }
|
||||
if ([lcsObject isEqual:object]) {
|
||||
lcsIndex++;
|
||||
} else {
|
||||
// All missing elements are treated as deletions for now and then revised later.
|
||||
[deletedIndexes addObject:@(i)];
|
||||
[deletedObjects addObject:object];
|
||||
|
||||
if (deleted[object] == nil) {
|
||||
deleted[object] = [NSMutableSet setWithObject:@(i)];
|
||||
} else {
|
||||
[deleted[object] addObject:@(i)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build everything that's not a delete. Changes come first, then moves, then insertions.
|
||||
// Moves are considered insertions of a previously deleted element, unless
|
||||
// that element was a part of a change.
|
||||
lcsIndex = 0;
|
||||
for (NSInteger i = 0; i < _result.count; i++) {
|
||||
id lcsObject;
|
||||
if (lcsIndex < lcs.count) { lcsObject = lcs[lcsIndex]; }
|
||||
id object = _result[i];
|
||||
if ([lcsObject isEqual:object]) {
|
||||
lcsIndex++;
|
||||
} else {
|
||||
// Insertion of a previously deleted element should be counted as a move.
|
||||
if (deleted[object].count > 0) {
|
||||
NSNumber *initialIndex = deleted[object].anyObject;
|
||||
[deleted[object] removeObject:initialIndex];
|
||||
[movedObjects addObject:object];
|
||||
[movedInitialIndexes addObject:initialIndex];
|
||||
[movedResultIndexes addObject:@(i)];
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're inserting at the same index that a deletion previously took place,
|
||||
// count it as an in-place change instead.
|
||||
if ([deleted[object] containsObject:@(i)]) {
|
||||
[changedObjects addObject:object];
|
||||
[changedIndexes addObject:@(i)];
|
||||
|
||||
// Changes can no longer be considered moves, so remove them from the moves dict.
|
||||
[deleted[object] removeObject:@(i)];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, this is just an insertion.
|
||||
[insertedIndexes addObject:@(i)];
|
||||
[insertedObjects addObject:object];
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, remove deletions that were later counted as moves/changes.
|
||||
NSMutableArray *oldDeletions = deletedObjects;
|
||||
NSMutableArray *oldIndexes = deletedIndexes;
|
||||
NSSet<NSNumber *> *changes = [NSSet setWithArray:movedInitialIndexes];
|
||||
changes = [changes setByAddingObjectsFromArray:changedIndexes];
|
||||
deletedObjects = [NSMutableArray arrayWithCapacity:oldDeletions.count];
|
||||
deletedIndexes = [NSMutableArray arrayWithCapacity:oldIndexes.count];
|
||||
for (NSInteger i = 0; i < oldDeletions.count; i++) {
|
||||
if ([changes containsObject:oldIndexes[i]]) { continue; }
|
||||
[deletedObjects addObject:oldDeletions[i]];
|
||||
[deletedIndexes addObject:oldIndexes[i]];
|
||||
}
|
||||
|
||||
_deletedIndexes = [deletedIndexes copy];
|
||||
_deletedObjects = [deletedObjects copy];
|
||||
|
||||
_insertedIndexes = [insertedIndexes copy];
|
||||
_insertedObjects = [insertedObjects copy];
|
||||
|
||||
_changedIndexes = [changedIndexes copy];
|
||||
_changedObjects = [changedObjects copy];
|
||||
|
||||
_movedInitialIndexes = [movedInitialIndexes copy];
|
||||
_movedResultIndexes = [movedResultIndexes copy];
|
||||
_movedObjects = [movedObjects copy];
|
||||
}
|
||||
|
||||
- (instancetype)initWithInitialArray:(NSArray<FIRDocumentSnapshot *> *)initial
|
||||
resultArray:(NSArray<FIRDocumentSnapshot *> *)result
|
||||
documentChanges:(NSArray<FIRDocumentChange *> *)documentChanges {
|
||||
// TODO(morganchen): this needs to be tested with valid documentChange arrays from Firestore.
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_initial = [initial copy];
|
||||
_result = [result copy];
|
||||
[self buildDiffsFromDocumentChanges:documentChanges];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)buildDiffsFromDocumentChanges:(NSArray<FIRDocumentChange *> *)documentChanges {
|
||||
NSMutableDictionary<NSString *, NSNumber *> *oldIndexes =
|
||||
[NSMutableDictionary dictionaryWithCapacity:_initial.count];
|
||||
NSMutableDictionary<NSString *, NSNumber *> *newIndexes =
|
||||
[NSMutableDictionary dictionaryWithCapacity:_result.count];
|
||||
|
||||
NSArray<FIRDocumentSnapshot *> *initial = _initial;
|
||||
NSArray<FIRDocumentSnapshot *> *result = _result;
|
||||
|
||||
// Ignore the FIRDocumentChange indexing, since we do our own
|
||||
for (NSInteger i = 0; i < _initial.count; i++) {
|
||||
oldIndexes[initial[i].documentID] = @(i);
|
||||
}
|
||||
for (NSInteger i = 0; i < _result.count; i++) {
|
||||
newIndexes[result[i].documentID] = @(i);
|
||||
}
|
||||
|
||||
NSMutableArray<NSNumber *> *deletedIndexes = [NSMutableArray array];
|
||||
NSMutableArray *deletedObjects = [NSMutableArray array];
|
||||
|
||||
NSMutableArray<NSNumber *> *insertedIndexes = [NSMutableArray array];
|
||||
NSMutableArray *insertedObjects = [NSMutableArray array];
|
||||
|
||||
NSMutableArray<NSNumber *> *changedIndexes = [NSMutableArray array];
|
||||
NSMutableArray *changedObjects = [NSMutableArray array];
|
||||
|
||||
NSMutableArray<NSNumber *> *movedInitialIndexes = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *movedResultIndexes = [NSMutableArray array];
|
||||
NSMutableArray *movedObjects = [NSMutableArray array];
|
||||
|
||||
NSMutableSet<FIRDocumentSnapshot *> *movedSnapshots = [NSMutableSet set];
|
||||
|
||||
for (FIRDocumentChange *change in documentChanges) {
|
||||
FIRDocumentSnapshot *snapshot = change.document;
|
||||
NSNumber *oldIndex = oldIndexes[snapshot.documentID];
|
||||
NSNumber *newIndex = newIndexes[snapshot.documentID];
|
||||
if (oldIndex == nil && newIndex == nil) { continue; }
|
||||
switch (change.type) {
|
||||
case FIRDocumentChangeTypeRemoved:
|
||||
// Ignore deletions that weren't in the original array.
|
||||
if (oldIndex == nil) { continue; }
|
||||
// Deletions that were then added again should be counted as moves.
|
||||
if (newIndex != nil) {
|
||||
[movedInitialIndexes addObject:oldIndex];
|
||||
[movedResultIndexes addObject:newIndex];
|
||||
[movedObjects addObject:snapshot];
|
||||
|
||||
// Keep track of which insertions we should ignore later.
|
||||
[movedSnapshots addObject:snapshot];
|
||||
} else {
|
||||
[deletedIndexes addObject:oldIndex];
|
||||
[deletedObjects addObject:snapshot];
|
||||
}
|
||||
continue;
|
||||
case FIRDocumentChangeTypeModified:
|
||||
// Don't try to reload changes that weren't in the initial and result arrays.
|
||||
if (newIndex == nil || oldIndex == nil) { continue; }
|
||||
// This should be counted as a move.
|
||||
if (![newIndex isEqualToNumber:oldIndex]) {
|
||||
[movedInitialIndexes addObject:oldIndex];
|
||||
[movedResultIndexes addObject:newIndex];
|
||||
[movedObjects addObject:snapshot];
|
||||
|
||||
// Keep track of which insertions we should ignore later.
|
||||
[movedSnapshots addObject:snapshot];
|
||||
} else {
|
||||
[changedIndexes addObject:oldIndex];
|
||||
[changedObjects addObject:snapshot];
|
||||
}
|
||||
continue;
|
||||
case FIRDocumentChangeTypeAdded:
|
||||
// Ignore insertions that were later removed.
|
||||
if (newIndex == nil) { continue; }
|
||||
// Ignore insertions of previously removed items, since those are
|
||||
// counted as moves.
|
||||
if (oldIndex != nil) { continue; }
|
||||
// Ignore insertions that we consider moves.
|
||||
if ([movedSnapshots containsObject:snapshot]) { continue; }
|
||||
[insertedIndexes addObject:newIndex];
|
||||
[insertedObjects addObject:snapshot];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_deletedIndexes = [deletedIndexes copy];
|
||||
_deletedObjects = [deletedObjects copy];
|
||||
|
||||
_insertedIndexes = [insertedIndexes copy];
|
||||
_insertedObjects = [insertedObjects copy];
|
||||
|
||||
_changedIndexes = [changedIndexes copy];
|
||||
_changedObjects = [changedObjects copy];
|
||||
|
||||
_movedInitialIndexes = [movedInitialIndexes copy];
|
||||
_movedResultIndexes = [movedResultIndexes copy];
|
||||
_movedObjects = [movedObjects copy];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
NSMutableString *result =
|
||||
[NSMutableString stringWithFormat:@"<%@: %p ", NSStringFromClass([self class]), self];
|
||||
|
||||
NSMutableString *deleted = [@"Deleted: (\n" mutableCopy];
|
||||
for (NSInteger i = 0; i < _deletedIndexes.count; i++) {
|
||||
NSNumber *index = _deletedIndexes[i];
|
||||
id object = _deletedObjects[i];
|
||||
[deleted appendFormat:@" %li, %@\n", (long)index.integerValue, object];
|
||||
}
|
||||
[deleted appendString:@")\n"];
|
||||
|
||||
[result appendString:deleted];
|
||||
|
||||
NSMutableString *moved = [@"Moved: (\n" mutableCopy];
|
||||
for (NSInteger i = 0; i < _movedInitialIndexes.count; i++) {
|
||||
NSNumber *initial = _movedInitialIndexes[i];
|
||||
NSNumber *final = _movedResultIndexes[i];
|
||||
id object = _movedObjects[i];
|
||||
[moved appendFormat:@" %li -> %li, %@\n",
|
||||
(long)initial.integerValue, (long)final.integerValue, object];
|
||||
}
|
||||
[moved appendString:@")\n"];
|
||||
|
||||
[result appendString:moved];
|
||||
|
||||
NSMutableString *changed = [@"Changed: (\n" mutableCopy];
|
||||
for (NSInteger i = 0; i < _changedIndexes.count; i++) {
|
||||
NSNumber *index = _changedIndexes[i];
|
||||
id object = _changedObjects[i];
|
||||
[changed appendFormat:@" %li, %@\n", (long)index.integerValue, object];
|
||||
}
|
||||
[changed appendString:@")\n"];
|
||||
|
||||
[result appendString:changed];
|
||||
|
||||
NSMutableString *inserted = [@"Inserted: (\n" mutableCopy];
|
||||
for (NSInteger i = 0; i < _insertedIndexes.count; i++) {
|
||||
NSNumber *index = _insertedIndexes[i];
|
||||
id object = _insertedObjects[i];
|
||||
[inserted appendFormat:@" %li, %@\n", (long)index.integerValue, object];
|
||||
}
|
||||
[inserted appendString:@")\n"];
|
||||
|
||||
[result appendString:inserted];
|
||||
[result appendString:@">"];
|
||||
|
||||
return [result copy];
|
||||
}
|
||||
|
||||
@end
|
||||
Generated
+106
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import <FirebaseFirestore/FirebaseFirestore.h>
|
||||
|
||||
#import "FUISnapshotArrayDiff.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class FUIBatchedArray;
|
||||
|
||||
@protocol FUIBatchedArrayDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Called after any new data is received or the batched array's query did change.
|
||||
*/
|
||||
- (void)batchedArray:(FUIBatchedArray *)array
|
||||
didUpdateWithDiff:(FUISnapshotArrayDiff<FIRDocumentSnapshot *> *)diff;
|
||||
|
||||
/**
|
||||
* Called before any new data is received or the batched array's query will change.
|
||||
*/
|
||||
- (void)batchedArray:(FUIBatchedArray *)array
|
||||
willUpdateWithDiff:(FUISnapshotArrayDiff<FIRDocumentSnapshot *> *)diff;
|
||||
|
||||
/**
|
||||
* Called when the array's query raises an error.
|
||||
*/
|
||||
- (void)batchedArray:(FUIBatchedArray *)array queryDidFailWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface FUIBatchedArray : NSObject
|
||||
|
||||
/**
|
||||
* The query that this array should fetch data from. If this property is changed while
|
||||
* observing, the array will fetch updates from the new query, diff those with the contents of
|
||||
* the old query, and pass an update to its delegate.
|
||||
*
|
||||
* This class will try to diff the contents of the entire query on every value event, so
|
||||
* make sure to limit your queries to an appropriate size to avoid performance issues.
|
||||
*/
|
||||
@property (nonatomic, readwrite, strong) FIRQuery *query;
|
||||
|
||||
/**
|
||||
* The delegate that should receive events from this array instance.
|
||||
*/
|
||||
@property (nonatomic, readwrite, weak) id<FUIBatchedArrayDelegate> delegate;
|
||||
|
||||
/**
|
||||
* The number of items in the array.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger count;
|
||||
|
||||
/**
|
||||
* The snapshots currently held in the array.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<FIRDocumentSnapshot *> *items;
|
||||
|
||||
/**
|
||||
* Initializes a batched array with a query and delegate.
|
||||
*/
|
||||
- (instancetype)initWithQuery:(FIRQuery *)query
|
||||
delegate:(nullable id<FUIBatchedArrayDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Retrieves the snapshot at a given index. Raises an out of bounds error if the index is
|
||||
* out of bounds.
|
||||
*/
|
||||
- (FIRDocumentSnapshot *)objectAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* See objectAtIndex:
|
||||
*/
|
||||
- (FIRDocumentSnapshot *)objectAtIndexedSubscript:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Starts observing the array's query. Before this method is called no events will be sent
|
||||
* and the array will be empty.
|
||||
*/
|
||||
- (void)observeQuery;
|
||||
|
||||
/**
|
||||
* Stops observing the array's query. The array's contents will remain and `observeQuery` may
|
||||
* be called again in the future to resume updates.
|
||||
*/
|
||||
- (void)stopObserving;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
#import "FUIBatchedArray.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* FUIFirestoreCollectionViewDataSource provides a class that conforms to the
|
||||
* UICollectionViewDataSource protocol which allows UICollectionViews to
|
||||
* adopt FUIFirestoreCollectionViewDataSource in order to provide a UICollectionView
|
||||
* synchronized to a Firestore reference or query.
|
||||
*/
|
||||
@interface FUIFirestoreCollectionViewDataSource : NSObject <UICollectionViewDataSource>
|
||||
|
||||
/**
|
||||
* The UICollectionView instance that operations (inserts, removals, moves,
|
||||
* etc.) are performed against. The data source does not claim ownership of
|
||||
* the collection view it populates. This collection view must be receiving data
|
||||
* from this data source otherwise data inconsistency crashes will occur.
|
||||
*/
|
||||
@property (nonatomic, readwrite, weak, nullable) UICollectionView *collectionView;
|
||||
|
||||
/**
|
||||
* The number of items in the data source.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger count;
|
||||
|
||||
/**
|
||||
* The snapshots in the data source.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<FIRDocumentSnapshot *> *items;
|
||||
|
||||
/**
|
||||
* The query from which this data source should load data. When set, the
|
||||
* data source will diff the contents of the old and new query and pass an
|
||||
* update to the collection view. Diffing is expensive, so try not to do
|
||||
* this with very large queries.
|
||||
*/
|
||||
@property (nonatomic, readwrite) FIRQuery *query;
|
||||
|
||||
/**
|
||||
* A closure that should be invoked when the query encounters a fatal error.
|
||||
* After this is invoked, the query is no longer valid and the data source should
|
||||
* be recreated.
|
||||
*/
|
||||
@property (nonatomic, copy, readwrite, nullable) void (^queryErrorHandler)(NSError *);
|
||||
|
||||
/**
|
||||
* Returns the snapshot at the given index. Throws an exception if the index is out of bounds.
|
||||
*/
|
||||
- (FIRDocumentSnapshot *)snapshotAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Initialize an instance of FUIFirestoreCollectionViewDataSource that populates
|
||||
* UICollectionViewCells with FIRDataSnapshots.
|
||||
* @param collection A FUICollection that the data source uses to pull snapshots
|
||||
* from Cloud Firestore.
|
||||
* @param populateCell A closure used by the data source to create the cells that
|
||||
* are displayed in the collection view. This closure is retained by the data
|
||||
* source, so if you capture self in the closure and also claim ownership of the
|
||||
* data source, be sure to avoid retain cycles by capturing a weak reference to self.
|
||||
* @return An instance of FUIFirestoreCollectionViewDataSource that populates
|
||||
* UICollectionViewCells with FIRDataSnapshots.
|
||||
*/
|
||||
- (instancetype)initWithCollection:(FUIBatchedArray *)collection
|
||||
populateCell:(UICollectionViewCell *(^)(UICollectionView *collectionView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *object))populateCell NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Initialize an unsorted instance of FUIFirestoreCollectionViewDataSource that populates
|
||||
* UICollectionViewCells with FIRDataSnapshots.
|
||||
* @param query A Firestore query to bind the data source to.
|
||||
* @param populateCell A closure used by the data source to create the cells that
|
||||
* are displayed in the collection view. This closure is retained by the data
|
||||
* source, so if you capture self in the closure and also claim ownership of the
|
||||
* data source, be sure to avoid retain cycles by capturing a weak reference to self.
|
||||
* @return An instance of FUIFirestoreCollectionViewDataSource that populates
|
||||
* UICollectionViewCells with FIRDataSnapshots.
|
||||
*/
|
||||
- (instancetype)initWithQuery:(FIRQuery *)query
|
||||
populateCell:(UICollectionViewCell *(^)(UICollectionView *collectionView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *object))populateCell;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Attaches the data source to a collection view and begins sending updates immediately.
|
||||
* @param view An instance of UICollectionView that the data source should push
|
||||
* updates to.
|
||||
*/
|
||||
- (void)bindToView:(UICollectionView *)view;
|
||||
|
||||
/**
|
||||
* Detaches the data source from a view and stops sending any updates.
|
||||
*/
|
||||
- (void)unbind;
|
||||
|
||||
@end
|
||||
|
||||
@interface UICollectionView (FUIFirestoreCollectionViewDataSource)
|
||||
|
||||
/**
|
||||
* Creates a data source, attaches it to the collection view, and returns it.
|
||||
* The returned data source is not retained by the collection view and must be
|
||||
* retained or it will be deallocated while still in use by the collection view.
|
||||
* @param query A Cloud Firestore query to bind the collection view to.
|
||||
* @param populateCell A closure used by the data source to create the cells
|
||||
* displayed in the collection view. The closure is retained by the returned
|
||||
* data source.
|
||||
* @return The created data source. This value must be retained while the collection
|
||||
* view is in use.
|
||||
*/
|
||||
- (FUIFirestoreCollectionViewDataSource *)bindToFirestoreQuery:(FIRQuery *)query
|
||||
populateCell:(UICollectionViewCell *(^)(UICollectionView *collectionView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *object))populateCell
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
#import "FUIBatchedArray.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* FUIFirestoreTableViewDataSource provides a class that conforms to the
|
||||
* UITableViewDataSource protocol which allows UITableViews to implement
|
||||
* FUIFirestoreTableViewDataSource in order to provide a UITableView synchronized
|
||||
* to a Firestore reference or query.
|
||||
*/
|
||||
@interface FUIFirestoreTableViewDataSource : NSObject <UITableViewDataSource>
|
||||
|
||||
/**
|
||||
* The UITableView instance that operations (inserts, removals, moves, etc.) are
|
||||
* performed against. This collection view must be receiving data from
|
||||
* this data source otherwise data inconsistency crashes will occur.
|
||||
*/
|
||||
@property (nonatomic, readwrite, weak, nullable) UITableView *tableView;
|
||||
|
||||
/**
|
||||
* The number of items in the data source.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger count;
|
||||
|
||||
/**
|
||||
* The snapshots in the data source.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<FIRDocumentSnapshot *> *items;
|
||||
|
||||
/**
|
||||
* The query from which this data source should load data. When set, the
|
||||
* data source will diff the contents of the old and new query and pass an
|
||||
* update to the collection view. Diffing is expensive, so try not to do
|
||||
* this with very large queries.
|
||||
*/
|
||||
@property (nonatomic, readwrite) FIRQuery *query;
|
||||
|
||||
/**
|
||||
* The type of animation that should be used for animated updates.
|
||||
* Defaults to UITableViewRowAnimationAutomatic.
|
||||
*/
|
||||
@property (nonatomic, readwrite) UITableViewRowAnimation animation;
|
||||
|
||||
/**
|
||||
* A closure that should be invoked when the query encounters a fatal error.
|
||||
* After this is invoked, the query is no longer valid and the data source should
|
||||
* be recreated.
|
||||
*/
|
||||
@property (nonatomic, copy, readwrite) void (^queryErrorHandler)(NSError *);
|
||||
|
||||
/**
|
||||
* Returns the snapshot at the given index. Throws an exception if the index is out of bounds.
|
||||
*/
|
||||
- (FIRDocumentSnapshot *)snapshotAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Initialize an instance of FUIFirestoreTableViewDataSource.
|
||||
* @param collection An FUICollection used by the data source to pull data
|
||||
* from Cloud Firestore.
|
||||
* @param populateCell A closure used by the data source to create/reuse
|
||||
* table view cells and populate their content. This closure is retained
|
||||
* by the data source, so if you capture self in the closure and also claim ownership
|
||||
* of the data source, be sure to avoid retain cycles by capturing a weak reference to self.
|
||||
* @return An instance of FUIFirestoreTableViewDataSource.
|
||||
*/
|
||||
- (instancetype)initWithCollection:(FUIBatchedArray *)collection
|
||||
populateCell:(UITableViewCell *(^)(UITableView *tableView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *object))populateCell NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize an instance of FUIFirestoreTableViewDataSource with contents ordered
|
||||
* by the query.
|
||||
* @param query A Firestore query to bind the data source to.
|
||||
* @param populateCell A closure used by the data source to create/reuse
|
||||
* table view cells and populate their content. This closure is retained
|
||||
* by the data source, so if you capture self in the closure and also claim ownership
|
||||
* of the data source, be sure to avoid retain cycles by capturing a weak reference to self.
|
||||
* @return An instance of FUIFirestoreTableViewDataSource.
|
||||
*/
|
||||
- (instancetype)initWithQuery:(FIRQuery *)query
|
||||
populateCell:(UITableViewCell *(^)(UITableView *tableView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *object))populateCell;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Attaches the data source to a table view and begins sending updates immediately.
|
||||
* @param view An instance of UITableView that the data source should push
|
||||
* updates to.
|
||||
*/
|
||||
- (void)bindToView:(UITableView *)view;
|
||||
|
||||
/**
|
||||
* Detaches the data source from a view and stops sending any updates.
|
||||
*/
|
||||
- (void)unbind;
|
||||
|
||||
@end
|
||||
|
||||
@interface UITableView (FUIFirestoreTableViewDataSource)
|
||||
|
||||
/**
|
||||
* Creates a data source, attaches it to the table view, and returns it.
|
||||
* The returned data source is not retained by the table view and must be
|
||||
* retained or it will be deallocated while still in use by the table view.
|
||||
* @param query A Cloud Firestore query to bind the table view to.
|
||||
* @param populateCell A closure used by the data source to create the cells
|
||||
* displayed in the table view. The closure is retained by the returned
|
||||
* data source.
|
||||
* @return The created data source. This value must be retained while the table
|
||||
* view is in use.
|
||||
*/
|
||||
- (FUIFirestoreTableViewDataSource *)bindToFirestoreQuery:(FIRQuery *)query
|
||||
populateCell:(UITableViewCell *(^)(UITableView *tableView,
|
||||
NSIndexPath *indexPath,
|
||||
FIRDocumentSnapshot *object))populateCell
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A lightweight class maintaining a view of a slice of an array without ever copying the
|
||||
* array's contents (except on init, and only if the array is mutable). Since the cost
|
||||
* to instantiating a new slice is so low, the slices themselves are immutable.
|
||||
*/
|
||||
@interface FUIArraySlice<__covariant ObjectType> : NSObject
|
||||
|
||||
/**
|
||||
* The index of the first element of the array slice. Inclusive.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger startIndex;
|
||||
|
||||
/**
|
||||
* The index following the last element of the array slice. Exclusive.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger endIndex;
|
||||
|
||||
/**
|
||||
* The number of elements in the array slice.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger count;
|
||||
|
||||
/**
|
||||
* The array storage backing the array slice instance.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *backingArray;
|
||||
|
||||
/**
|
||||
* Initializes an array slice from an array, a start index, and a length.
|
||||
*/
|
||||
- (instancetype)initWithArray:(NSArray<ObjectType> *)array
|
||||
startIndex:(NSInteger)start
|
||||
length:(NSInteger)length NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Initializes an array slice from an array, a start index (inclusive), and an
|
||||
* end index (exclusive).
|
||||
*/
|
||||
- (instancetype)initWithArray:(NSArray<ObjectType> *)array
|
||||
startIndex:(NSInteger)start
|
||||
endIndex:(NSInteger)end;
|
||||
|
||||
/**
|
||||
* Initializes an array slice encompassing the entire array argument.
|
||||
*/
|
||||
- (instancetype)initWithArray:(NSArray<ObjectType> *)array;
|
||||
|
||||
/**
|
||||
* Initializes an empty array slice with an empty backing array.
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* The object from the backing array in the array slice. Throws an invalid argument exception
|
||||
* if the index is out of bounds.
|
||||
*/
|
||||
- (ObjectType)objectAtIndex:(NSInteger)index;
|
||||
|
||||
- (ObjectType)objectAtIndexedSubscript:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Returns a subslice from the specified index to the end of the receiver.
|
||||
*/
|
||||
- (FUIArraySlice *)suffixFromIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* An immutable unordered pair class that returns equivalent iff two pairs contain the same
|
||||
* elements, regardless of order.
|
||||
*/
|
||||
@interface FUIUnorderedPair<__covariant ObjectType> : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, readonly) ObjectType left;
|
||||
@property (nonatomic, readonly) ObjectType right;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Instantiates an unordered pair with two elements. This is a C function and not
|
||||
* an initializer purely because initializers are more verbose.
|
||||
*/
|
||||
FUIUnorderedPair *FUIUnorderedPairMake(id left, id right);
|
||||
|
||||
|
||||
@interface FUILCS<__covariant ObjectType> : NSObject
|
||||
|
||||
/**
|
||||
* Returns the longest common subsequence of two arrays. This method is not useful in itself,
|
||||
* but it's exposed here for testability. O(m * n) complexity, where m and n are the sizes of
|
||||
* the input arrays.
|
||||
*/
|
||||
+ (NSArray *)lcsWithInitialArray:(NSArray<ObjectType> *)initial
|
||||
resultArray:(NSArray<ObjectType> *)result;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@class FIRDocumentChange, FIRDocumentSnapshot;
|
||||
|
||||
/**
|
||||
* Constructs a diff from two arrays. Initialization is O(m * n), where m and n are the lengths
|
||||
* of the input arrays. Construction is expensive, and diffs are not cached.
|
||||
*/
|
||||
@interface FUISnapshotArrayDiff<__covariant ObjectType> : NSObject
|
||||
|
||||
/** The initial array. */
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *initial;
|
||||
|
||||
/** The resulting array. */
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *result;
|
||||
|
||||
/** An array of indexes of deleted items relative to the initial array. */
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *deletedIndexes;
|
||||
|
||||
/** An array of objects deleted from the initial array. */
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *deletedObjects;
|
||||
|
||||
/** An array of the initial indexes of moved items. */
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *movedInitialIndexes;
|
||||
|
||||
/** An array of the final indexes of moved items. */
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *movedResultIndexes;
|
||||
|
||||
/** An array of objects that were moved. */
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *movedObjects;
|
||||
|
||||
/** An array of indexes of objects that were changed. */
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *changedIndexes;
|
||||
|
||||
/** An array of the resulting objects that were changed. */
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *changedObjects;
|
||||
|
||||
/** An array of indexes of objects that were inserted, relative to the final array. */
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *insertedIndexes;
|
||||
|
||||
/** An array of inserted objects. */
|
||||
@property (nonatomic, readonly) NSArray<ObjectType> *insertedObjects;
|
||||
|
||||
/**
|
||||
* Creates a diff between two arrays. This operation is relatively expensive;
|
||||
* O(n x m) for arrays of length n and m.
|
||||
*/
|
||||
- (instancetype)initWithInitialArray:(NSArray<ObjectType> *)initialArray
|
||||
resultArray:(NSArray<ObjectType> *)resultArray;
|
||||
|
||||
/**
|
||||
* Creates a diff between two arrays, using the document changes array to speed up
|
||||
* performance.
|
||||
*/
|
||||
- (instancetype)initWithInitialArray:(NSArray<FIRDocumentSnapshot *> *)initial
|
||||
resultArray:(NSArray<FIRDocumentSnapshot *> *)result
|
||||
documentChanges:(NSArray<FIRDocumentChange *> *)documentChanges;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for FirebaseFirestoreUI.
|
||||
FOUNDATION_EXPORT double FirebaseFirestoreUIVersionNumber;
|
||||
|
||||
//! Project version string for FirebaseFirestoreUI.
|
||||
FOUNDATION_EXPORT const unsigned char FirebaseFirestoreUIVersionString[];
|
||||
|
||||
#import "FUISnapshotArrayDiff.h"
|
||||
#import "FUIBatchedArray.h"
|
||||
#import "FUIFirestoreCollectionViewDataSource.h"
|
||||
#import "FUIFirestoreTableViewDataSource.h"
|
||||
Generated
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
Generated
+156
@@ -0,0 +1,156 @@
|
||||
# FirebaseUI for iOS — UI Bindings for Firebase
|
||||
|
||||
          
|
||||
|
||||
FirebaseUI is an open-source library for iOS that allows you to quickly connect common UI elements to the [Firebase](https://firebase.google.com?utm_source=FirebaseUI-iOS) database for data storage, allowing views to be updated in realtime as they change, and providing simple interfaces for common tasks like displaying lists or collections of items.
|
||||
|
||||
Additionally, FirebaseUI simplifies Firebase authentication by providing easy to use auth methods that integrate with common identity providers like Facebook, Twitter, and Google as well as allowing developers to use a built in headful UI for ease of development.
|
||||
|
||||
FirebaseUI clients are also available for [Android](https://github.com/firebase/FirebaseUI-Android) and [web](https://github.com/firebase/firebaseui-web).
|
||||
|
||||

|
||||
|
||||
## Installing FirebaseUI for iOS
|
||||
|
||||
FirebaseUI supports iOS 10.0+ and Xcode 11+. We recommend using [CocoaPods](https://cocoapods.org/pods/FirebaseUI), add
|
||||
the following to your `Podfile`:
|
||||
|
||||
```ruby
|
||||
pod 'FirebaseUI', '~> 8.0' # Pull in all Firebase UI features
|
||||
```
|
||||
|
||||
If you don't want to use all of FirebaseUI, there are multiple subspecs which can selectively install subsets of the full feature set:
|
||||
|
||||
```ruby
|
||||
# Only pull in Firestore features
|
||||
pod 'FirebaseUI/Firestore', '~> 8.0'
|
||||
|
||||
# Only pull in Database features
|
||||
pod 'FirebaseUI/Database', '~> 8.0'
|
||||
|
||||
# Only pull in Storage features
|
||||
pod 'FirebaseUI/Storage', '~> 8.0'
|
||||
|
||||
# Only pull in Auth features
|
||||
pod 'FirebaseUI/Auth', '~> 8.0'
|
||||
|
||||
# Only pull in Facebook login features
|
||||
pod 'FirebaseUI/Facebook', '~> 8.0'
|
||||
|
||||
# Only pull in Google login features
|
||||
pod 'FirebaseUI/Google', '~> 8.0'
|
||||
|
||||
# Only pull in Phone Auth login features
|
||||
pod 'FirebaseUI/Phone', '~> 8.0'
|
||||
```
|
||||
|
||||
If you're including FirebaseUI in a Swift project, make sure you also have:
|
||||
|
||||
```ruby
|
||||
platform :ios, '10.0'
|
||||
use_frameworks!
|
||||
```
|
||||
|
||||
Otherwise, you can include the FirebaseUI Xcode project from this repo in
|
||||
your project. You also need to
|
||||
[add the Firebase framework](https://firebase.google.com/docs/ios/setup)
|
||||
to your project.
|
||||
|
||||
## Documentation
|
||||
|
||||
The READMEs for components of FirebaseUI can be found in their respective
|
||||
project folders.
|
||||
|
||||
- [Auth](Auth/README.md)
|
||||
- [PhoneAuth](PhoneAuth/README.md)
|
||||
- [Database](Database/README.md)
|
||||
- [Firestore](Firestore/README.md)
|
||||
- [Storage](Storage/README.md)
|
||||
|
||||
## Local Setup
|
||||
|
||||
If you'd like to contribute to FirebaseUI for iOS, you'll need to run the
|
||||
following commands to get your environment set up:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/firebase/FirebaseUI-iOS.git
|
||||
$ cd FirebaseUI-iOS
|
||||
$ cd Auth # or PhoneAuth, Database, etc
|
||||
$ pod install
|
||||
```
|
||||
|
||||
Alternatively you can use `pod try FirebaseUI` to install the Objective-C or Swift sample projects.
|
||||
|
||||
## Sample Project Configuration
|
||||
|
||||
You'll have to configure your Xcode project in order to run the samples.
|
||||
|
||||
1. Your Xcode project should contain a `GoogleService-Info.plist`, downloaded from [Firebase console](https://console.firebase.google.com) when you add your app to a Firebase project.<br>
|
||||
Copy the `GoogleService-Info.plist` into the sample project folder (`samples/obj-c/GoogleService-Info.plist` or `samples/swift/GoogleService-Info.plist`).
|
||||
|
||||
1. Update URL Types.<br>
|
||||
Go to `Project Settings -> Info tab -> Url Types` and update values for:
|
||||
+ `REVERSED_CLIENT_ID` (get value from `GoogleService-Info.plist`)
|
||||
+ `fb{your-app-id}` (put Facebook App Id)
|
||||
|
||||
1. Update `Info.plist` with Facebook configuration values
|
||||
+ `FacebookAppID -> {your-app-id}` (put Facebook App Id)
|
||||
|
||||
1. Enable Keychain Sharing.<br>
|
||||
Facebook SDK requires keychain sharing.<br>
|
||||
This can be done here: `Project Settings -> Capabilities -> KeyChain Sharing -> ON`
|
||||
|
||||
1. Don't forget to configure your Firebase App Database using [Firebase console](https://console.firebase.google.com).<br>
|
||||
Database should contain appropriate read/write permissions and folders (`objc_demo-chat` and `swift_demo-chat` respectively)
|
||||
|
||||
1. In Order to use `Phone Auth` provider you should [Configure Push Notifications](#configure-apple-push-notifications)
|
||||
|
||||
#### Configure Apple Push Notifications
|
||||
|
||||
##### Enable silent push notifications in Xcode
|
||||
|
||||
* `Push Notification` - Under `Capabilities` tab in your app target choose `Push Notifications` and put the switch to the `On` position.
|
||||
* `Background Mode` - Under `Capabilities` tab in your app target choose `Background Modes` put the switch to the `On` position. In the list of available modes select `Background fetch` and `Remote notifications` (If available).
|
||||
|
||||
##### Upload APNS Certificate to Firebase
|
||||
|
||||
1. Create your `Provisioning APNS SSL Certificates` by following the steps on the following link.
|
||||
https://firebase.google.com/docs/cloud-messaging/ios/certs
|
||||
|
||||
1. Upload your `APNS Certificate` to Firebase:
|
||||
+ Inside your project in the Firebase console, select the gear icon, select `Project Settings`, and then select the `Cloud Messaging` tab.
|
||||
+ Select the `Upload Certificate` button for your development certificate, your production certificate, or both. At least one is required.
|
||||
+ For each certificate, select the `.p12 file`, and provide the password, if any. Make sure the `bundle ID` for this certificate matches the `bundle ID` of your app. Select `Save`.
|
||||
|
||||
## Contributing to FirebaseUI
|
||||
|
||||
### Contributor License Agreements
|
||||
|
||||
We'd love to accept your sample apps and patches! Before we can take them, we
|
||||
have to jump a couple of legal hurdles.
|
||||
|
||||
Please fill out either the individual or corporate Contributor License Agreement
|
||||
(CLA).
|
||||
|
||||
* If you are an individual writing original source code and you're sure you
|
||||
own the intellectual property, then you'll need to sign an [individual CLA]
|
||||
(https://developers.google.com/open-source/cla/individual).
|
||||
* If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a [corporate CLA]
|
||||
(https://developers.google.com/open-source/cla/corporate).
|
||||
|
||||
Follow either of the two links above to access the appropriate CLA and
|
||||
instructions for how to sign and return it. Once we receive it, we'll be able to
|
||||
accept your pull requests.
|
||||
|
||||
### Contribution Process
|
||||
|
||||
1. Submit an issue describing your proposed change to the repo in question.
|
||||
1. The repo owner will respond to your issue promptly.
|
||||
1. If your proposed change is accepted, and you haven't already done so, sign a
|
||||
Contributor License Agreement (see details above).
|
||||
1. Fork the desired repo, develop and test your code changes.
|
||||
1. Ensure that your code adheres to the existing style of the library to which
|
||||
you are contributing.
|
||||
1. Ensure that your code has an appropriate set of unit tests which all pass.
|
||||
1. Submit a pull request
|
||||
Reference in New Issue
Block a user