This repo shows two examples of syncing data with UITableView and Firebase.
FirebaseDataSource uses CocoaPods to manage the Firebase dependency.
pod install
The RefViewController demonstrates the process of manually syncing data from Firebase to a UITableView.
This view controller has a ref: Firebase
property and a syncArray: [FDataSnapshot!]
property. In the
viewDidAppear
lifecycle method, Firebase observers are attached that mutate the syncArray
whenever an add or remove
event has occurred.
// MARK: Properties
var ref: Firebase!
var syncArray: [FDataSnapshot]!
override func viewDidLoad() {
super.viewDidLoad()
ref = Firebase(url: "https://uitableview-firebase.firebaseio-demo.com/values")
syncArray = [FDataSnapshot]()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Start syncing events when the view is loaded
appendToListWhenRemoteItemIsAdded(ref) // Check the source for implementations
removeFromListWhenRemoteItemIsRemoved(ref)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
// Stop syncing when the view is off screen
ref.removeAllObservers()
}
This example only demonstrates events for added and removed. This example is also not portable. If we need to create
another UITableViewController that syncs data, we'll be writing the same code. To fix this problem we can create a
synchronized array that alerts us when items have been added, removed, changed, or moved in the array. The second
example, DataSourceViewController
, uses a synchronized array.
The DataSourceViewController uses the FirebaseDataSource
class to sync data between Firebase and the UITableView.
The 'FirebaseDataSource' class is a small wrapper around a FirebaseArray
that provides the NSIndexPath
of the
item of the data update.
The FirebaseArray
has a delegate which fires off functions when FEventType
events have
occurred (.ChildAdded
, .ChildChanged
, .ChildRemoved
, .ChildMoved
).
@objc protocol FirebaseArrayDelegate {
optional func firebaseArray(firebaseArray: [FDataSnapshot], indexAdded: Int, data: FDataSnapshot)
optional func firebaseArray(firebaseArray: [FDataSnapshot], indexChanged: Int, data: FDataSnapshot)
optional func firebaseArray(firebaseArray: [FDataSnapshot], indexRemoved: Int, data: FDataSnapshot)
optional func firebaseArray(firebaseArray: [FDataSnapshot], oldIndex: Int, newIndex: Int, data: FDataSnapshot)
}
class SomeClass : FirebaseArrayDelegate {
init(ref: Firebase) {
var syncArray = FirebaseArray(ref: ref)
// set the delegate to the class to listen for data updates
syncArray.delegate = self
}
func firebaseArray(list: [FDataSnapshot], indexAdded: Int, data: FDataSnapshot) {
// do something when the item has been added
}
}
Each delegate method provides the current list, the index of the item, and the snapshot from Firebase. The index as
an integer isn't useful for UIKit controls like UITableView
and UICollectionView
. UIKit controls require the
NSIndexPath
type for modifying rows. This is where we use the FirebaseDataSource
class.
The FirebaseDataSource
provides a thin wrapper around the FirebaseArray
that provides delegate methods
that return the snapshot as well as the NSIndexPath
.
@objc protocol FirebaseDataSourceDelegate {
optional func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemAddedAtIndexPath: NSIndexPath, data: FDataSnapshot)
optional func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemChangedAtIndexPath: NSIndexPath, data: FDataSnapshot)
optional func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemRemovedAtIndexPath: NSIndexPath, data: FDataSnapshot)
optional func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemMovedAtIndexPath: NSIndexPath, toIndexPath: NSIndexPath, data: FDataSnapshot)
}
We can use this delegate protocol on UIKit controls like UITableViewController
and CollectionViewController
.
class DataSourceViewController: UITableViewController, FirebaseDataSourceDelegate {
var dataSource: FirebaseDataSource!
override func viewDidLoad() {
super.viewDidLoad()
var ref = Firebase(url: "https://uitableview-firebase.firebaseio-demo.com/values")
dataSource = FirebaseDataSource(ref: ref)
dataSource.delegate = self
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// When the view "wakes up" we'll need to start from a fresh
// set of data on the tableView
tableView.reloadData()
// Once the tableView has been reloaded we can start syncing
// from Firebase again
dataSource.startSync()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
// When the view goes off the screen we'll need to reserve
// resources by canceling and sync events
dataSource.stopSync()
}
}
From here we can wire up the tableView delegate and firebaseDataSource delegate methods.
First, we'll need to provide the row count in the numberOfRowsInSection
delegate method.
The data source should have a length or a count method to provide the rows in section count.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
Dequeue the cell prototype in the cellForRowAtIndexPath
delegate method. The tableView delegate has
a cellForRowAtIndexPath
method that fires off every time an item from the data source is updated.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("<SOME-CELL-IDENTIFIER>") as! UITableViewCell
var data: FDataSnapshot! = dataSource[indexPath.row] // dataSource is of [FDataSnapshot]
cell.textLabel?.text = data.value["text"] // Assume we are syncing an object with a string property of "text"
return cell
}
When an item has been added, we can insert the rows into the tableView.
func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemAddedAtIndexPath: NSIndexPath, data: FDataSnapshot) {
tableView.insertRowsAtIndexPaths([itemAddedAtIndexPath], withRowAnimation: .None)
}
When an item has been changed, we can reload the tableView at the specified NSIndexPath
.
func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemChangedAtIndexPath: NSIndexPath, data: FDataSnapshot) {
tableView.reloadRowsAtIndexPaths([itemChangedAtIndexPath], withRowAnimation: .None)
}
When an item has been removed, we can remove the item from the tableView at the specified NSIndexPath
.
func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemRemovedAtIndexPath: NSIndexPath, data: FDataSnapshot) {
tableView.deleteRowsAtIndexPaths([itemRemovedAtIndexPath], withRowAnimation: .None)
}
When an item has been moved, we can specify the old NSIndexPath
position and then the new NSIndePath
position to move the item to.
func firebaseDataSource(firebaseDataSource: FirebaseDataSource, itemMovedAtIndexPath: NSIndexPath, toIndexPath: NSIndexPath, data: FDataSnapshot) {
tableView.moveRowAtIndexPath(itemMovedAtIndexPath, toIndexPath: toIndexPath)
}
Copyright 2015 David East
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.