0

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

}
Mazel Tov
  • 2,064
  • 14
  • 26

2 Answers2

0

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
    }
}
Kumuluzz
  • 2,012
  • 1
  • 13
  • 17
  • Thank you for the reply, do I say it right: If I will create the cell manually with the "refresh" function I can also set `self.paginationEnabled = false`? The thing is that when i remove `override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return calculateHeightForRowAtIndexPath(indexPath) }` The app works perfect but the cell height is messed up, and the last cell is the cell Load more. What I dont understand is that the **heightForRowAtIndexPath** is calculating just the cell height so why it disabled the pagination... – Mazel Tov Jun 09 '15 at 14:57
  • 1
    You have to insert the code snippet for the method above in between the other methods. I will edit the answer to reflect this. – Kumuluzz Jun 09 '15 at 15:06
  • testing your code you posted was the first thing I did [screenshot](https://www.anony.ws/image/D6cZ), but the last cell with Load more data is not showing... – Mazel Tov Jun 09 '15 at 15:20
  • @Kumuluzz I have just gone through your answer here and on Mazel's other post, & I think I have a similar issue but Im using a normal TVC and not a PFQTVC I also am using UITableViewAutomaticDimension to resize my cells, but it doesn't seem to work correctly(If you like you can check my latest question) any help or suggestions would be very much appreciated! :) –  Jan 14 '16 at 18:49
  • @Theo, this question has nothing to do with your problem. This question is about pagination and yours is about sizing your cells dynamically. But feel free to attach a link to the specific question and then I'll have a look – Kumuluzz Jan 15 '16 at 11:47
  • Oh ok, well if you can have a look that would be great! I have already accepted an answer, but it's not really what I need as its very jump and messy now! if you read the comments on the answer, you'll see I'm trying to achieve kind of like instagrams feed(Now that they allow you to post any size pic and not just square) http://stackoverflow.com/questions/34795120/uitableviewautomaticdimension-cells-resize-after-scroll-or-pull-to-refresh –  Jan 15 '16 at 12:02
0

Using Parse 1.11 with iOS 9.2 and Xcode 7.2 Parse Pagination works perfectly. Problems surface when the user override some funcs used by Parse itself without properly managing the "Load More ..." row added by Parse. In my case I needed to override tableView-canEditRowAtIndexPath to determine whether the current user can or cannot delete the row according to the object's ACL. My initial func was:

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {

    if let curUser = PFUser.currentUser() {
        let currentObject = objects![indexPath.row]
        if let acl = currentObject.ACL {
            return acl.getWriteAccessForUser(curUser)
        } else {
           return true
        }
    }
    return true
}

but I got the exception of indexpath out of bounds when the Load More line was met during list scrolling. Problem was solved adding this test:

    if (indexPath.row == self.objects!.count) { // row "Load More ..."
        return true
    }

Without this code the "Load More ..." row was not added by Parse!! So the complete correct overriding func is:

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {

    if (indexPath.row == self.objects!.count) { // row "Load More ..."
        return true
    }
    if let curUser = PFUser.currentUser() {
        let currentObject = objects![indexPath.row]
        if let acl = currentObject.ACL {
            return acl.getWriteAccessForUser(curUser)
        } else {
           return true
        }
    }
    return true
}

Generally speaking all overridden funcs including heightForRowAtIndexpath, must take care of the extra line added by Parse when pagination is enabled.

HTH

Roberto Targa

R. Targa
  • 11
  • 3