askaale askaale - 2 months ago 47
Swift Question

Swift,Firebase: Updating feed(TableView) on scroll, [like Instagram]

For the past few days, I have been trying to create an Instagram-like feed for my app. To be more specific: load new posts (5) each time the user updates the feed from the bottom.

I am currently using Firebase to both store and display my data.

My code so far, looks like this:

var ref:FIRDatabaseReference!

var dict = [String:Any]()
var posts = [[String:Any]]()

override func viewDidLoad() {
super.viewDidLoad()

tableView.delegate = self
tableView.dataSource = self

ref = FIRDatabase.database().reference()

// Do any additional setup after loading the view, typically from a nib.
}

override func viewDidAppear(animated: Bool) {

loadValues()

}

func loadValues() {

dict.removeAll()
posts.removeAll()


ref.child("posts").queryOrderedByChild("timeCreated").queryLimitedToLast(5).observeEventType(.ChildAdded) { (snapshot:FIRDataSnapshot) in

if let timeCreated = snapshot.value!["timeCreated"] as? Int {
self.dict["timeCreated"] = timeCreated
}

if let postText = snapshot.value!["postText"] as? String {
self.dict["postText"] = postText
}


self.posts.append(self.dict)


self.tableView.reloadData()

}




}


func scrollViewDidScroll(scrollView: UIScrollView) {

if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height {

//tableView.tableFooterView!.hidden = true
let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
pagingSpinner.startAnimating()
pagingSpinner.hidesWhenStopped = true
pagingSpinner.sizeToFit()
tableView.tableFooterView = pagingSpinner

//loadMore(5)

} else {

let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
pagingSpinner.stopAnimating()
pagingSpinner.hidesWhenStopped = true
pagingSpinner.sizeToFit()
pagingSpinner.hidden = true
tableView.tableFooterView = pagingSpinner
tableView.tableFooterView?.hidden = true


}


}


func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return posts.count

}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)

if let postText = posts[indexPath.row]["postText"] as? String {
cell.textLabel!.text = postText
}

return cell

}

func loadMore(increment:Int) {

//What should go in here?

}


So what I am trying to do here - is that I am detecting when the user has scrolled to the bottom (in my scrollViewDidScroll function. Then I am sinply displaying the activity indicator, and calling the function loadMore(5) where 5 is the amount of new posts that I want to display.

So here I have two problems. The timeCreated variable is simply a timestamp, where I have ten records (1-10, where 10 is the newest, and 1 is the oldest). With this code that I have now, the tableView displays the data in an ascending view, starting at 5 and ending at 10.

I have tried to reverse the array of dictionaries (post) by simply doing a .reverse() before appending the dict in the loadValues function. as I simply want it to display 10 at the top, and 5 at the bottom.

The second problem I have is that I can't really seem to find a good and effective way of updating the tableView (adding another 5 records). I have tried to simply just have a global variable with the default value of 5, and then on loadMore simply plus it by five, and then do a removeAll() on both the dict and posts - with no luck (the tableView scrolls to the top, which I don't want to). I have also tried to play with both queryLimitedTolast and queryLimitedToFirst where I ended up duplicating some data.

So in other words, I also need to check that the user can in fact load 5 new unique posts (or ex. 3, if there's only 3 unique posts left).

Does anyone have any ideas on how I would approach this?

Help would be greatly appreciated, as I have struggled with this for the past two days now.

Answer

If you are using tableView update your DataSource instead of adding a row at a particular index. using struct is a common aproach.

struct dataSourceS(){

     var timecreated : String!
     var posttext : String!

     init(time : String!, pText : String!){

          self.timecreated = time
          self.posttext = pText
       }
    } 
  • Declare an array of type dataSourceS

         var dataS = [dataSourceS]()
    
  • For knowing that how many posts you have already retrived , you need to keep the index of each post in the the post node itself.Which can be done by counting the no of children in the post node and incrementing it by one.Or creating a complete separate node of

    noOfPosts: 100, //Lets say there are 100 posts in your DB 
    
     Posts : {
      Post1:{
    
         timecreated :23123, 
         postText : asdasdasd,
         index : 12               
    
          },
    
       Post2:{
    
         timecreated :23163, 
         postText : asdasddasdasd,
         index : 13              
    
          },.....
     }
    

    Store your retrieved data in this struct

       func retrieveData(start_Value : UInt!,end_Value : UInt!,completionBlock : ((feed : [dataSourceS]) -> Void)){
           parentRef.queryOrderedByChild("index").queryStartingAtValue(start_Value).queryEndingAtValue(end_Value).observeEventType(.Value, withBlock: { snapshot in
    
               if let timevariable = snapshot.value!["timecreated"] as? String{
    
                    if let postvariable = snapshot.value!["postText"] as? String{
    
                            let temp = dataSourceS.init(name : timevariable, brand : postvariable)
                               self.dataS.insert(temp, atIndex: 0)
                               completionBlock(feed : dataS)
    
                    }
               }
    
         })
    
  • Calling this function:-

    retrieveData(noOfTotalPost - decrement + 5,end_Value : noOfTotalPost - decrement,completionBlock : {(feedData)   
    
          if feedData.count == decrement{
    
                  feedData.reverse() 
                  customTableView.reloadData()
                 }
           })
    

Where:-

noOfTotalPost = Total no of posts in the post node, which you can easily get by your snapshot's snapshot.childrenCount or retrieving from noOfPosts node in your DB.

decrement is an A.P : 5,10,15,20,25......//till 95 in this case

So that :- (Lets say your total posts are 100)

noOfTotalPost - decrement + 5 : 100,95,90,85,.....

noOfTotalPost - decrement : 95,90,85,80,.....

Mid query part is just an extension of @Jay's answer : - http://stackoverflow.com/a/37100087/6297658

Your final code will look something like this:-

 import UIKit
 import Firebase
 import FirebaseDatabase
 import FirebaseAuth


 struct dataSourceS{

   var timecreated : Int!
   var posttext : String!

  init(time : Int!, pText : String!){

    self.timecreated = time
    self.posttext = pText
     }
 }


 class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{

var ref:FIRDatabaseReference!

var dataS = [dataSourceS]()

var noOfTotalPost : UInt = 0

@IBOutlet var customtableView: UITableView!
override func viewDidLoad() {
    super.viewDidLoad()

    customtableView.delegate = self
    customtableView.dataSource = self

    ref = FIRDatabase.database().reference()


}

override func viewWillAppear(animated: Bool) {

    super.viewWillAppear(animated)

    noOfTotalPost = 0


    ref.child("posts").observeSingleEventOfType(.Value) { (snapshot:FIRDataSnapshot) in

        self.noOfTotalPost = snapshot.childrenCount
        self.loadMore()
    }


}

@IBAction func loadData(sender: AnyObject) {

}

func retrieveData(start_Value : UInt!,end_Value : UInt!,completionBlock : ((feed : [dataSourceS]) -> Void)){

    print(start_Value)
    print(end_Value)
    print(self.noOfTotalPost)

    FIRDatabase.database().reference().child("posts").queryOrderedByChild("index").queryStartingAtValue(start_Value).queryEndingAtValue(end_Value).observeEventType(.Value) { (snapshot:FIRDataSnapshot) in

        for each in snapshot.value as! NSMutableDictionary{

            if let timevariable = each.value["timeCreated"] as? Int {

                if let postvariable = each.value["postText"] as? String {


                    let index = self.dataS.count
                    let temp = dataSourceS.init(time: timevariable, pText: postvariable)
                    self.dataS.insert(temp, atIndex: 0)
                    completionBlock(feed: self.dataS)
                }

            }


        }
    }
}

func scrollViewDidScroll(scrollView: UIScrollView) {

    if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height {

        //tableView.tableFooterView!.hidden = true
        let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
        pagingSpinner.startAnimating()
        pagingSpinner.hidesWhenStopped = true
        pagingSpinner.sizeToFit()
        customtableView.tableFooterView = pagingSpinner
        loadMore()

    } else {

        let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
        pagingSpinner.stopAnimating()
        pagingSpinner.hidesWhenStopped = true
        pagingSpinner.sizeToFit()
        pagingSpinner.hidden = true
        customtableView.tableFooterView = pagingSpinner
        customtableView.tableFooterView?.hidden = true


    }


}

@IBAction func SendPush(sender: AnyObject) {
       }

func loadMore() {

    let initialFeeds = dataS.count

    retrieveData( noOfTotalPost - 5 - UInt(dataS.count), end_Value: noOfTotalPost - UInt(dataS.count)) { (feed) in

        print(self.dataS.count)
        if feed.count == initialFeeds + 5{

            self.customtableView.reloadData()


        }
    }
}



func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return dataS.count

}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = customtableView.dequeueReusableCellWithIdentifier("Cell") as! ccCell

    cell.textLabel!.text = dataS[indexPath.row].posttext
    return cell

  }


}