Search code examples
iosswiftuitableviewparse-platformpfquery

Using PFQuery inside cellForRowAtIndexPath


I was thinking about PFQuery.

I'm developing an App that shows a Feed to the Users and it also displays a Like counter for each Post (like a Facebook App or Instagram App).

So in my PFQueryTableViewController I have my main query, that basically show all the Posts:

override func queryForTable() -> PFQuery {
    let query = PFQuery(className: "Noticias")
    query.orderByDescending("createdAt")   
    return query
}

And I use another query to count the number of Likes on another Class in Parse that contais all the Likes.

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
    var cell = tableView.dequeueReusableCellWithIdentifier("FeedCellIdentifier") as! FeedCell!
    if cell == nil {
        cell = FeedCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCellIdentifier")
    }

    let query2 = PFQuery(className:"commentsTable")
    query2.whereKey("newsColumn", equalTo: object!)
    query2.findObjectsInBackgroundWithBlock {
        (objectus: [PFObject]?, error: NSError?) -> Void in

        if error == nil {

            let quantidade = objectus!.count

            let commentQuantidade = String(quantidade)

            cell.comentariosLabel.text = commentQuantidade
        } else {
            // Log details of the failure
            print("Error: \(error!) \(error!.userInfo)")
        }
    }

This way to code works, and I achieve what I want, but! I know that I'm reusing cells, I know that this block of code is called everytime a cell appear.

And I know those facts:

  1. A lot of query requests is sent to Parse Cloud, everytime I scroll the tableview

  2. It's possible to see the values changing, when I'm scrolling the tableview, for example, because I'm reusing the cells a post has a value of my previous cell and then with the new query it's refreshed, this works but not look good for user experience.

So, my main doubt is, is it the right way to code? I think not, and I just want another point of view or an idea.

Thanks.

EDIT 1

As I said I've updated my count method to countObjectsInBackgroundWithBlock instead of findObjectsInBackgroundWithBlock but I'm not able to move the query to the ViewDidLoad, because I use the object to check exactly how many comments each Post have.

EDIT 2 I've embed the query to count the number of comments for each post and printing the results, now I'm think my code is better than the previous version, but I'm not able to pass the result to a label because I'm receiving a error:

Use of unresolved identifier 'commentCount'

I'm reading some documentations about Struct

Follows my updated code bellow:

import UIKit
import Social

class Functions: PFQueryTableViewController, UISearchBarDelegate {

override func shouldAutorotate() -> Bool {
    return false
}

var passaValor = Int()
let swiftColor = UIColor(red: 13, green: 153, blue: 252)
struct PostObject{
    let post : PFObject
    let commentCount : Int
}

var posts : [PostObject] = []


// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) {
    super.init(style: style, className: className)
}

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)!
    // The className to query on
    self.parseClassName = "Noticias"

    // The key of the PFObject to display in the label of the default cell style
    self.textKey = "text"

    // Uncomment the following line to specify the key of a PFFile on the PFObject to display in the imageView of the default cell style
    self.imageKey = "image"

    // Whether the built-in pull-to-refresh is enabled
    self.pullToRefreshEnabled = true

    // Whether the built-in pagination is enabled
    self.paginationEnabled = true

    // The number of objects to show per page
    self.objectsPerPage = 25
}

// Define the query that will provide the data for the table view

override func queryForTable() -> PFQuery {
    let query = super.queryForTable()

    return query
}



override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(true)
    loadObjects()
}

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func viewDidLoad() {
    super.viewDidLoad()
    // navigationBarItems()


    let query = PFQuery(className:"Noticias")
    query.findObjectsInBackgroundWithBlock {
        (objects: [PFObject]?, error: NSError?) -> Void in

        // The find succeeded.
        print("Successfully retrieved \(objects!.count) scores.")
        // Do something with the found objects
        if let objects = objects {
            for object in objects {
                let queryCount = PFQuery(className:"commentsTable")
                queryCount.whereKey("newsColumn", equalTo: object)
                queryCount.countObjectsInBackgroundWithBlock {
                    (contagem: Int32, error: NSError?) -> Void in
                    let post = PostObject(object, commentCount:commentCount)
                    posts.append(post)
                    print("Post \(object.objectId!) has \(contagem) comments")
                }
                self.tableView.reloadData()
            }
        }
    }

    //Self Sizing Cells
    tableView.estimatedRowHeight = 350.0
    tableView.rowHeight = UITableViewAutomaticDimension


}



// Define the query that will provide the data for the table view

//override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("FeedCellIdentifier") as! FeedCell!
if cell == nil {
    cell = FeedCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCellIdentifier")
}

cell?.parseObject = object

if let assuntoNoticia = object?["assunto"] as? String {
    cell?.assuntoNoticia?.text = assuntoNoticia
}


if let pontos = object?["pontos"] as? Int {
    let pontosPosts = String(pontos)
    cell?.pontosLabel?.text = String(pontosPosts)
}


if let zonaLabel = object?["zona"] as? String {
    cell?.zonaLabel?.text = zonaLabel
}


if let criticidade = object?["criticidade"] as? String {
    if criticidade == "Problema"{

        cell.criticidadeNoticia.backgroundColor = UIColor.redColor()
    } else {
        cell.criticidadeNoticia.backgroundColor = UIColor.greenColor()
    }
}

return cell
}
}

And the result of print:

Successfully retrieved 5 scores.

Post wSCsTv8OnH has 4 comments

Post LbwBfjWPod has 0 comments

Post fN4ISVwqpz has 0 comments

Post 1rXdQr2A1F has 1 comments

Post eXogPeTfNu has 0 comments


Solution

  • Better practice would be to query all data on view load saving it into model and then read data from it on table view scroll. When processing query you can show downloading indicator or placeholder data. When query is complete you'll call tableView.reloadData()

    You can accomplish this by creating a new variable like this:

     var cellModels : [PFObject] = []
    

    In your query2.findObjectsInBackgroundWithBlock:

     for object in objectus{
       self.cellModels.append(object)
     }
     self.tableView.reloadData()
    

    And in cellForRowAtIndexPath:

    let model = cellModels[indexPath.row]
    // configure cell according to model
    // something like cell.textLabel.text = model.text
    

    P.S You should take a look at method countObjectsInBackgroundWithBlock if you only need to get count of objects. Because if there're a lot of e.g comments findObjectsInBackgroundWithBlock will return maximum of 1000 objects and still you won't be downloading whole objects, only one number this will speed up query and spare user's cellular plan.

    Update: Also if you need to store numbers of comments you can create simple struct like this:

    struct PostObject{
     let post : PFObject
     let commentCount : Int
    }
    
    var posts : [PostObject] = []
    

    And when you query for you posts you loop through received objects and populate posts array.

    for object in objects{
      // create countObjectsInBackgroundWithBlock query to get comments count for object
      // and in result block create
      let post = PostObject(object, commentCount:commentCount)
      posts.append(post)
    
    } 
    tableView.reloadData()
    

    And in cellForRowAtIndexPath:

    let post = posts[indexPath.row]
    cell.postCountLabel.text = String(post.commentCount)
    // configure cell accordingly