diff --git a/FirebaseDatabaseUI/FUICollectionViewDataSource.h b/FirebaseDatabaseUI/FUICollectionViewDataSource.h index cb6005427bb..e94d7683d5d 100644 --- a/FirebaseDatabaseUI/FUICollectionViewDataSource.h +++ b/FirebaseDatabaseUI/FUICollectionViewDataSource.h @@ -41,16 +41,26 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite, weak, nullable) UICollectionView *collectionView; /** - * The callback to populate a subclass of UICollectionViewCell with an object - * provided by the datasource. + * The number of items in the data source. */ -@property (strong, nonatomic, readonly) UICollectionViewCell *(^populateCellAtIndexPath) - (UICollectionView *collectionView, NSIndexPath *indexPath, FIRDataSnapshot *object); +@property (nonatomic, readonly) NSUInteger count; /** - * The number of items in the data source. + * The snapshots in the data source. */ -@property (nonatomic, readonly) NSUInteger count; +@property (nonatomic, readonly) NSArray *items; + +/** + * 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. + */ +- (FIRDataSnapshot *)snapshotAtIndex:(NSInteger)index; /** * Initialize an instance of FUICollectionViewDataSource that populates diff --git a/FirebaseDatabaseUI/FUICollectionViewDataSource.m b/FirebaseDatabaseUI/FUICollectionViewDataSource.m index c9e67d52d44..5be1a0f2faa 100644 --- a/FirebaseDatabaseUI/FUICollectionViewDataSource.m +++ b/FirebaseDatabaseUI/FUICollectionViewDataSource.m @@ -23,10 +23,17 @@ @import FirebaseDatabase; -@interface FUICollectionViewDataSource () +@interface FUICollectionViewDataSource () @property (nonatomic, readonly, nonnull) id collection; +/** + * The callback to populate a subclass of UICollectionViewCell with an object + * provided by the datasource. + */ +@property (strong, nonatomic, readonly) UICollectionViewCell *(^populateCellAtIndexPath) + (UICollectionView *collectionView, NSIndexPath *indexPath, FIRDataSnapshot *object); + @end @implementation FUICollectionViewDataSource @@ -40,6 +47,7 @@ - (instancetype)initWithCollection:(id)collection self = [super init]; if (self) { _collection = collection; + _collection.delegate = self; _populateCellAtIndexPath = populateCell; } return self; @@ -57,6 +65,14 @@ - (NSUInteger)count { return self.collection.count; } +- (NSArray *)items { + return self.collection.items; +} + +- (FIRDataSnapshot *)snapshotAtIndex:(NSInteger)index { + return [self.collection snapshotAtIndex:index]; +} + - (void)bindToView:(UICollectionView *)view { self.collectionView = view; view.dataSource = self; @@ -92,6 +108,12 @@ - (void)array:(FUIArray *)array didMoveObject:(id)object toIndexPath:[NSIndexPath indexPathForItem:toIndex inSection:0]]; } +- (void)array:(id)array queryCancelledWithError:(NSError *)error { + if (self.queryErrorHandler != NULL) { + self.queryErrorHandler(error); + } +} + #pragma mark - UICollectionViewDataSource methods - (nonnull UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView diff --git a/FirebaseDatabaseUI/FUITableViewDataSource.h b/FirebaseDatabaseUI/FUITableViewDataSource.h index 826d1bfe0b5..c9dba5612fb 100644 --- a/FirebaseDatabaseUI/FUITableViewDataSource.h +++ b/FirebaseDatabaseUI/FUITableViewDataSource.h @@ -42,15 +42,26 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite, weak, nullable) UITableView *tableView; /** - * The callback used by the data source to populate the table view. + * The number of items in the data source. */ -@property (strong, nonatomic, readonly) UITableViewCell *(^populateCell) - (UITableView *tableView, NSIndexPath *indexPath, FIRDataSnapshot *snap); +@property (nonatomic, readonly) NSUInteger count; /** - * The number of items in the data source. + * The snapshots in the data source. */ -@property (nonatomic, readonly) NSUInteger count; +@property (nonatomic, readonly) NSArray *items; + +/** + * 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. + */ +- (FIRDataSnapshot *)snapshotAtIndex:(NSInteger)index; /** * Initialize an instance of FUITableViewDataSource. diff --git a/FirebaseDatabaseUI/FUITableViewDataSource.m b/FirebaseDatabaseUI/FUITableViewDataSource.m index 8d832eb71bc..69bfe9967ed 100644 --- a/FirebaseDatabaseUI/FUITableViewDataSource.m +++ b/FirebaseDatabaseUI/FUITableViewDataSource.m @@ -23,7 +23,7 @@ @import FirebaseDatabase; -@interface FUITableViewDataSource () +@interface FUITableViewDataSource () @property (strong, nonatomic, readwrite) UITableViewCell *(^populateCell) (UITableView *tableView, NSIndexPath *indexPath, FIRDataSnapshot *snap); @@ -43,6 +43,7 @@ - (instancetype)initWithCollection:(id)collection self = [super init]; if (self != nil) { _collection = collection; + _collection.delegate = self; _populateCell = populateCell; } return self; @@ -60,6 +61,14 @@ - (NSUInteger)count { return self.collection.count; } +- (NSArray *)items { + return self.collection.items; +} + +- (FIRDataSnapshot *)snapshotAtIndex:(NSInteger)index { + return [self.collection snapshotAtIndex:index]; +} + - (void)bindToView:(UITableView *)view { self.tableView = view; view.dataSource = self; @@ -103,6 +112,12 @@ - (void)array:(FUIArray *)array didMoveObject:(id)object toIndexPath:[NSIndexPath indexPathForRow:toIndex inSection:0]]; } +- (void)array:(id)array queryCancelledWithError:(NSError *)error { + if (self.queryErrorHandler != NULL) { + self.queryErrorHandler(error); + } +} + #pragma mark - UITableViewDataSource methods - (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { diff --git a/FirebaseDatabaseUITests/FUICollectionViewDataSourceTest.m b/FirebaseDatabaseUITests/FUICollectionViewDataSourceTest.m index 7fd6ff4c4db..d69838f7a42 100644 --- a/FirebaseDatabaseUITests/FUICollectionViewDataSourceTest.m +++ b/FirebaseDatabaseUITests/FUICollectionViewDataSourceTest.m @@ -55,6 +55,9 @@ - (void)setUp { return cell; }]; + // Removing this line causes the tests to crash. + NSLog(@"%lu", (unsigned long)[self.collectionView numberOfItemsInSection:0]); + [self.observable populateWithCount:10]; } @@ -70,6 +73,11 @@ - (void)testItHasACount { XCTAssert(count == 10, @"expected data source to have 10 elements after 10 insertions, but got %lu", count); } +- (void)testItReturnsSnapshots { + id snap = [self.dataSource snapshotAtIndex:0]; + XCTAssert(snap != nil, @"expected snapshot to exist"); +} + - (void)testItPopulatesCells { UICollectionViewCell *cell = [self.dataSource collectionView:self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; diff --git a/FirebaseDatabaseUITests/FUITableViewDataSourceTest.m b/FirebaseDatabaseUITests/FUITableViewDataSourceTest.m index eb3306ec9ab..91625b46476 100644 --- a/FirebaseDatabaseUITests/FUITableViewDataSourceTest.m +++ b/FirebaseDatabaseUITests/FUITableViewDataSourceTest.m @@ -67,6 +67,11 @@ - (void)testItHasACount { XCTAssert(count == 10, @"expected data source to have 10 elements after 10 insertions, but got %lu", count); } +- (void)testItReturnsSnapshots { + id snap = [self.dataSource snapshotAtIndex:0]; + XCTAssert(snap != nil, @"expected snapshot to exist"); +} + - (void)testItPopulatesCells { UITableViewCell *cell = [self.dataSource tableView:self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:5 inSection:0]]; diff --git a/FirebaseUI.podspec b/FirebaseUI.podspec index a4b540b453b..2b06a3eb8d5 100644 --- a/FirebaseUI.podspec +++ b/FirebaseUI.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |s| s.name = 'FirebaseUI' - s.version = '2.0.3' + s.version = '3.0.0' s.summary = 'UI binding libraries for Firebase.' s.homepage = 'https://github.com/firebase/FirebaseUI-iOS' s.license = { :type => 'Apache 2.0', :file => 'FirebaseUIFrameworks/LICENSE' } - s.source = { :http => 'https://github.com/firebase/FirebaseUI-iOS/releases/download/v2.0.3/FirebaseUIFrameworks.zip' } + s.source = { :http => 'https://github.com/firebase/FirebaseUI-iOS/releases/download/v3.0.0/FirebaseUIFrameworks.zip' } s.author = 'Firebase' s.platform = :ios s.ios.deployment_target = '8.0' diff --git a/samples/swift/FirebaseUI-demo-swift/Samples/Chat/ChatViewController.swift b/samples/swift/FirebaseUI-demo-swift/Samples/Chat/ChatViewController.swift index 9a20188be98..3d47ffbecd2 100644 --- a/samples/swift/FirebaseUI-demo-swift/Samples/Chat/ChatViewController.swift +++ b/samples/swift/FirebaseUI-demo-swift/Samples/Chat/ChatViewController.swift @@ -62,18 +62,15 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { self.query = self.chatReference.queryLimited(toLast: 50) self.collectionViewDataSource = - FUICollectionViewDataSource(query: self.query!, - view: self.collectionView, - populateCell: { (view, indexPath, snap) -> UICollectionViewCell in - let cell = view.dequeueReusableCell(withReuseIdentifier: ChatViewController.reuseIdentifier, - for: indexPath) as! ChatCollectionViewCell - let chat = Chat(snapshot: snap)! - cell.populateCellWithChat(chat, user: self.user, maxWidth: self.view.frame.size.width) - return cell - }) - self.collectionView.dataSource = self.collectionViewDataSource + self.collectionView.bind(to: self.query!) { (view, indexPath, snap) -> UICollectionViewCell in + let cell = view.dequeueReusableCell(withReuseIdentifier: ChatViewController.reuseIdentifier, + for: indexPath) as! ChatCollectionViewCell + let chat = Chat(snapshot: snap)! + cell.populateCellWithChat(chat, user: self.user, maxWidth: self.view.frame.size.width) + return cell + } - // FirebaseArray has a delegate method `childAdded` that could be used here, + // FUIArray has a delegate method `childAdded` that could be used here, // but unfortunately FirebaseCollectionViewDataSource uses the FUICollection // delegate methods to update its own internal state, so in order to scroll // on new insertions we still need to use the query directly. @@ -94,13 +91,13 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { // Notification boilerplate to handle keyboard appearance/disappearance NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillShow), - name: NSNotification.Name.UIKeyboardWillShow, - object: nil) + selector: #selector(keyboardWillShow), + name: NSNotification.Name.UIKeyboardWillShow, + object: nil) NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillHide), - name: NSNotification.Name.UIKeyboardWillHide, - object: nil) + selector: #selector(keyboardWillHide), + name: NSNotification.Name.UIKeyboardWillHide, + object: nil) } @objc fileprivate func didTapSend(_ sender: AnyObject) { @@ -174,8 +171,9 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { } fileprivate func scrollToBottom(animated: Bool) { - let count = self.collectionViewDataSource.collectionView(self.collectionView, numberOfItemsInSection: 0) - let indexPath = IndexPath(row: count - 1, section: 0) + let count = Int(self.collectionViewDataSource.count) + guard count > 0 else { return } + let indexPath = IndexPath(item: count - 1, section: 0) self.collectionView.scrollToItem(at: indexPath, at: .bottom, animated: animated) } @@ -186,7 +184,7 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { let heightPadding: CGFloat = 16 let width = self.view.frame.size.width - let blob = self.collectionViewDataSource.object(at: UInt((indexPath as NSIndexPath).row))! + let blob = self.collectionViewDataSource.snapshot(at: indexPath.item) let text = Chat(snapshot: blob)!.text let rect = ChatCollectionViewCell.boundingRectForText(text, maxWidth: width) diff --git a/samples/swift/Podfile b/samples/swift/Podfile index 153d7cc35da..40547144d1f 100644 --- a/samples/swift/Podfile +++ b/samples/swift/Podfile @@ -1,7 +1,7 @@ target 'FirebaseUI-demo-swift' do use_frameworks! - pod 'FirebaseUI', :podspec => 'https://raw.githubusercontent.com/firebase/FirebaseUI-iOS/master/FirebaseUI.podspec' + pod 'FirebaseUI', :podspec => '../../FirebaseUI.podspec' target 'FirebaseUI-demo-swiftTests' do inherit! :search_paths