Search code examples
iosswiftparse-platformpaginationpfquerytableviewcontrolle

PFQueryTableViewController pagination doesn't work with heightForRowAtIndexPath


I am using parse.com framework with Swift and in PFQueryTableViewController when I set the pagination it won't work. If the DB has less rows than the number set in objectPerPage it works fine, but if there are more rows and when I run the app it keeps showing the loading screen and nothing is downloaded, when I do "swipe as refresh" it crash as Error

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]

ImagesTableViewController.swift

import UIKit
import Parse
import ParseUI
import Bolts

class ImagesTableViewController: PFQueryTableViewController {
@IBAction func unwindToSegue (segue : UIStoryboardSegue) {}

// 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)

    // Configure the PFQueryTableView
    self.parseClassName = "Image"
    self.pullToRefreshEnabled = true
    self.paginationEnabled = true
    self.objectsPerPage = 5

}

// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery {
    var query = PFQuery(className: "Image")
    query.whereKey("deleted", notEqualTo: 1)
    query.orderByDescending("createdAt")
    return query
}

//override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {

    var cell = tableView.dequeueReusableCellWithIdentifier("ImageCell") as! ImageTVCell!
    if cell == nil {
        cell = ImageTVCell(style: UITableViewCellStyle.Default, reuseIdentifier: "ImageCell")
    }

    // Extract values from the PFObject to display in the table cell HEADLINE
    if let caption = object?["caption"] as? String {
        cell?.headlineLabel?.text = caption
    }

    // Display image
    var initialThumbnail = UIImage(named: "question")
    cell.postImageView.image = initialThumbnail
    if let thumbnail = object?["image"] as? PFFile {
        cell.postImageView.file = thumbnail
        cell.postImageView.loadInBackground()
    }

    return cell
}

// if I remove this code pagination work but the cell height is wrong
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return calculateHeightForRowAtIndexPath(indexPath)
}


func calculateHeightForRowAtIndexPath(indexPath: NSIndexPath) -> CGFloat {
    if let ratio = objectAtIndexPath(indexPath)?["aspect"] as? Float {
        println("Ratio: \(ratio)")
        return tableView.bounds.size.width / CGFloat(ratio)
    } else {
        return 50.0
    }
}


@IBAction func addNewPhotoButton(sender: UIBarButtonItem) {
    self.tabBarController?.tabBar.hidden = true
    self.performSegueWithIdentifier("showUploadNewImage", sender: self)
}

}

Solution

  • This problem occurs because of PFQueryTableViewController's implementation of the method tableView:numberOfRowsInSection from the UITableViewDataSource. I've copy/pasted it from the GitHub repo containing PFQueryTableViewController.m

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        NSInteger count = [self.objects count];
        if ([self _shouldShowPaginationCell]) {
            count += 1;
        }
        return count;
    }
    

    It simply returns the count of objects to display (which makes sense), but if pagination is enabled, then it requires for an extra cell to be shown. This means you have to manually created another cell with the text "Load more data" or something like that, which would trigger a refresh.


    A way to overcome this is simply by overriding tableView:numberOfRowsInSection yourself with the following:

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.objects!.count
    }
    

    UPDATE 1

    The prebuilt Parse pagination button was gone in previous answer


    Use the following code snippet for calculating the height of the cells to display the prebuilt Parse pagination button

    func calculateHeightForRowAtIndexPath(indexPath: NSIndexPath) -> CGFloat {
        // Special case for pagination, using the pre-built one by Parse
        if (indexPath.row >= objects!.count) { return 50.0 }
    
        // Determines the height if an image ratio is present
        if let ratio = objectAtIndexPath(indexPath)?["aspect"] as? Float {
            println("Ratio: \(ratio)")
            return tableView.bounds.size.width / CGFloat(ratio)
        } else {
            return 50.0
        }
    }