Reputation: 4533
I am loading data from Firebase Database. I am trying to load data while user scrolls. For example if he has scrolled about 20 cells
then I want to load a little more about 5 cells
. I am trying to achieve it like this but it is not working:
func parseSnusBrands(){
let ref = FIRDatabase.database().reference().child("Snuses").child("Brands")
ref.queryOrderedByKey().queryLimitedToLast(20).observeEventType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
if let all = (snapshot.value?.allKeys)! as? [String]{
self.snusBrandsArray = all
self.snusBrandsTableView.reloadData()
}
}
})
}
and to detect how many cells are scrolled:
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 20 {
let ref = FIRDatabase.database().reference().child("Snuses").child("Brands")
ref.queryOrderedByKey().queryLimitedToLast(20).observeEventType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
if let all = (snapshot.value?.allKeys)! as? [String]{
self.snusBrandsArray = all
self.snusBrandsTableView.reloadData()
}
}
})
}
}
This is my Firebase structure:
Is there better solution or should I keep trying like this?
Here you can see the whole code. I know that it lacks of OO but I am learning :)
Upvotes: 1
Views: 2069
Reputation: 1378
You may want to use offset
to achieve pagination in your app.
First, add an order
key in your child path, may be current time stamp of insertion so that you can order it and use it in pagination.
For eg.
"brands": {
"catch" : {
...
...
"pOrder" : 555
},
"etan" : {
...
...
"pOrder" : 444
},
"general" : {
...
...
"pOrder" : 555
}.
.....
}
Now, to achieve pagination you would retrieve 21 records and in those records, the first 20 will be the records to use in table view, while the last record will be the offset
that will be used to retrieve next set of records.
Create a function that fetches data from firebase
:
// MARK: Retrieving Data: Firebase
/*
retrieve current set of posts sorted by updated time of posts
*/
func retrievePost(offset: NSNumber, callFlag: Bool, completion: (result: AnyObject?, error: NSError?)->()){
// As this method is called from viewDidLoad and fetches 20 records at first.
// Later when user scrolls down to bottom, its called again
let postsRef = ref.child(kDBPostRef)
var startingValue:AnyObject?
// starting value will be nil when this method is called from viewDidLoad as the offset is not set
if callFlag{
if offset == 0{
startingValue = nil
}
else{
startingValue = offset
}
} else{
// get offset from the offsetArray
startingValue = self.findOffsetFromArray()
}
// sort records by pOrder fetch offset+1 records
self.refHandler = postsRef.queryOrderedByChild("pOrder").queryStartingAtValue(startingValue).queryLimitedToFirst(kPostLimit + 1).observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
// flag is for setting the last record/ 21st as offset
var flag = 0
let tempPost = NSMutableSet()
// iterate over children and add to tempPost
for item in snapshot.children {
// check for offet, the last row(21st) is offset ; Do not add last element in the main table list
flag += 1
if flag == 21 && callFlag == true{
// this row is offset
self.kOffset = item.value?["pOrder"] as! NSNumber
self.offSetArray?.append(self.kOffset)
continue
}
// create Post object
let post = Post(snapshot: item as! FIRDataSnapshot)
// append to tempPost
tempPost.addObject(post)
}
// return to the closure
completion(result:tempPost, error:nil)
})
}
And call this updateNewRecords
method from viewDidLoad
, passing 0
to retrieve first 20 records.
func updateNewRecords(offset:NSNumber, callFlag: Bool){
self.retrievePost(offset,callFlag:callFlag) { (result,error) -> Void in
// let tempArray = result as! [Post]
let oldSet = Set(self.posts)
var unionSet = oldSet.union(result as! Set<Post>)
unionSet = unionSet.union(unionSet)
self.posts = Array(unionSet)
self.postsCopy = self.posts
// print(self.posts.count)
self.posts.sortInPlace({ $0.pOrder> $1.pOrder})
self.reloadTableData()
}
}
Again when the user scrolls to bottom in the table view
, call this method, updateNewRecords
passing the offset, to retrieve next set of 20 records.
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// UITableView only moves in one direction, y axis
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
// Change 10.0 to adjust the distance from bottom
if maximumOffset - currentOffset <= 10.0 {
self.updateNewRecords(self.kOffset, callFlag:true)
}
}
You can maintain an array offsetArray
for updating table values when your data is modified in firebase
.
For e.g. suppose some data is modified somewhere in your table, in that case observeEventType
will be called and you should fetch only those range of records from firebase
. You can achieve it using maintaining an array of offsets
. So when any record is updated, the retrievePost
will be called with the corresponding offset
from offsetArray
. In normal case, ( while retrieving data from viewDidLoad
and scrollViewDidEndDragging
), the offset will be kOffset
and in rest of the cases(while listening to data change), the offset will be from offsetArray
.
You can define a function that will return the offset
value for a particular updated column:
// find the offset from the offsetDict
func findOffsetFromArray() -> NSNumber{
let idx = self.kClickedRow/20 // kClickedRow is the updated row in the table view
return self.offSetArray![idx]
}
You can also modify retrievePost
method and pass an extra parameter to retrieve a certain number of records.
Upvotes: 2