askaale askaale - 2 months ago 29
Swift Question

Swift,Firebase: Updating tableViewUI with feed when reached at bottom [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 dataS {

var postData : String!
var index_Initial : Int!

init(post : String!, ind : Int!)
{
 self.postData = post
 self.index_Initial = ind
  }

}
  • Declare an array of type dataSourceS

         var dataFeed= [dataS]()
    
  • 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:{
    
    
         text : asdasdasd,
         index : 12               
    
          },
    
       Post2:{
    
    
         text : asdasddasdasd,
         index : 13              
    
          },.....
     }
    

Your final code will look something like this:-

 import UIKit
import Firebase

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

var dataFeed = [dataS]()
let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
var totalNoOfPost : Int!



@IBOutlet weak var customTableView: UITableView!




override func viewDidLoad() {
    super.viewDidLoad()

    customTableView.delegate = self
    customTableView.dataSource = self
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    FIRDatabase.database().reference().child("Posts").observeSingleEventOfType(.Value, withBlock: {(snap) in

        if let postDict = snap.value as? [String:AnyObject]{

             self.totalNoOfPost = postDict.count

                self.loadMore()
        }
    })

}

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return dataFeed.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = customTableView.dequeueReusableCellWithIdentifier("customCell") as! customTableViewCell
    if dataFeed.count > 0{
    cell.poatLabel.text = dataFeed[indexPath.row].postData
    }
    return cell
}

func loadMore(){

    let initialFeedCount : Int = dataFeed.count

    if totalNoOfPost - initialFeedCount - 4 > 0{

    FIRDatabase.database().reference().child("Posts").queryOrderedByChild("index").queryStartingAtValue(totalNoOfPost - initialFeedCount - 4).queryEndingAtValue(totalNoOfPost - initialFeedCount).observeEventType(.Value, withBlock: {(recievedSnap) in

        if recievedSnap.exists(){

        for each in recievedSnap.value as! [String:AnyObject]{
            let temp = dataS.init(post: each.1["text"] as! String, ind : each.1["index"] as! Int)
            self.dataFeed.insert(temp, atIndex: 5 * Int(self.dataFeed.count/5))
            self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
               if self.dataFeed.count == initialFeedCount+5{
                self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
                self.customTableView.reloadData()

            }
          }

         }
        }, withCancelBlock: {(err) in

            print(err.localizedDescription)


      })

    }else if totalNoOfPost - initialFeedCount - 4 <= 0{


        FIRDatabase.database().reference().child("Posts").queryOrderedByChild("index").queryStartingAtValue(0).queryEndingAtValue(totalNoOfPost - initialFeedCount).observeEventType(.Value, withBlock: {(recievedSnap) in

            if recievedSnap.exists(){

            for each in recievedSnap.value as! [String:AnyObject]{

                let temp = dataS.init(post: each.1["text"] as! String, ind : each.1["index"] as! Int)

                self.dataFeed.insert(temp, atIndex: 5 * Int(self.dataFeed.count/5))
                self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
                if self.dataFeed.count == initialFeedCount+4{
                   self.dataFeed.sortInPlace({$0.index_Initial > $1.index_Initial})
                    self.customTableView.reloadData()
                        self.pagingSpinner.stopAnimating()
                }
              }
            }else{

            self.pagingSpinner.stopAnimating()
            }

            }, withCancelBlock: {(err) in

                print(err.localizedDescription)


        })
    }
}

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    if (indexPath.row + 1) == dataFeed.count {
        print("Displayed the last row!")


                    pagingSpinner.startAnimating()
                    pagingSpinner.hidesWhenStopped = true
                    pagingSpinner.sizeToFit()
                    customTableView.tableFooterView = pagingSpinner
                    loadMore()
    }
}


}


struct dataS {

var postData : String!
var index_Initial : Int!

init(post : String!, ind : Int!)
{
 self.postData = post
 self.index_Initial = ind
  }

}