0

I have a UITableViewController (instead of a PFQueryTableViewController) to display my query results and I have an array storing texts. Since the query would fetch huge amount of data, I would like my tableView to load more results once the user scroll to the bottom. There are many solutions out there but they're either in JSON or ObjectiveC and they seem really vague to me, as I am just a beginner.

class queryResultsViewController: UITableViewController {

var texts = [String]()


override func viewDidLoad() {
    super.viewDidLoad()

    let query = PFQuery(className: "allPosts")

    query.whereKey("userId", equalTo: (PFUser.currentUser()?.objectId)!)
    query.orderByDescending("createdAt")

    query.findObjectsInBackgroundWithBlock { (posts, error) -> Void in

        if let posts = posts {

            self.texts.removeAll(keepCapacity: true)

            for post in posts {

                self.captionOne.append(post["text"] as! String)

                self.tableView.reloadData()
            }
        }
    }
}

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return texts.count
}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! theCell

    cell.TextView.text = texts[indexPath.row]

    return cell
}
CubeJockey
  • 2,209
  • 8
  • 24
  • 31
H. Lamb
  • 299
  • 1
  • 5
  • 15

3 Answers3

1

To detect when the user has scrolled to the bottom of the UITableView, you can implement the UIScrollView delegate method scrollViewDidScroll:

An example implementation (converted to Swift from: https://stackoverflow.com/a/5627837/3933375)

override func scrollViewDidScroll(scrollView: UIScrollView) {
    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let inset = scrollView.contentInset
    let y = CGFloat(offset.y + bounds.size.height - inset.bottom)
    let h = CGFloat(size.height)

    let reload_distance = CGFloat(10)
    if(y > (h + reload_distance)) {
        print("load more rows")
    }
}

When this fires you can then download more results from parse, add them to the datasource of your UITableView and call reload data.

Also, looking at your code you probably need to make a call to dispatch_async as you're trying to update the UI in a background block e.g.

dispatch_async(dispatch_get_main_queue()) { () -> Void in
        self.tableview.reloadData()
    }



Edit
To load more results from Parse

let query = PFQuery(className: "allPosts")

query.whereKey("userId", equalTo: (PFUser.currentUser()?.objectId)!)
query.orderByDescending("createdAt")

query.limit = 50 // or your choice of how many to download at a time (defaults to 100)
query.skip = 50 // This will skip the first 50 results and return the next limit after. If

query.makeRequest......



In your completion handler, make sure you append the results to the overall datasource (in your case texts), and call reload data.

Community
  • 1
  • 1
Ollie
  • 1,926
  • 1
  • 20
  • 35
  • thanks for the reply. about downloading more results, how should I construct my PFQuery? like how can i make sure i'm downloading additional data rather than a whole lot including the ones that I already downloaded? – H. Lamb Feb 23 '16 at 15:02
  • @H.Lamb I've edited my answer, in short you can use `query.limit` to set the amount of results you want to receive, and `query.skip` to get the next z elements after the first set. – Ollie Feb 23 '16 at 15:11
  • hey i'm sorry to bother, but just found out that using the scrollViewDidScroll fund would fire the query twice? after reloading tableview, two sets of the same query results are loaded to the tableview, any idea why this might be happening? i turned off parse local datastore to avoid cacheThenNetwork, don't know if this is related – H. Lamb Mar 05 '16 at 09:16
  • 1
    I shouldn't think that would effect it. I would add a boolean variable to the top of the file for tracking when parse is making a request, then only make another when that one has finished. So in `scrollViewDidScroll`, you would first check that variable to see if there is currently a request in progress – Ollie Mar 05 '16 at 21:42
  • Just don't forget to set your `requestInProgress` variable to false again when the request finishes. Give it a go and let me know if you have any issues and i'll add something more detailed to the answer. – Ollie Mar 05 '16 at 21:43
  • thats a great idea! but how can i keep track of whether parse is making a request? – H. Lamb Mar 06 '16 at 08:21
  • Just before you start making the request (`query.findObjectsInBackgroundWithBlock`), set a boolean variable to true, as you've just asked Parse to make a request. Then when it's finished (when inside the block), you can set the boolean variable back. Just make sure you check the boolean, for example: `if !requestInProgress { makeRequest...` – Ollie Mar 06 '16 at 08:38
  • just tried out what you've suggested! the solution's simple and perfect! thanks so much! – H. Lamb Mar 06 '16 at 08:49
0

How about displaying just 20 (for instance) rows and add a "Next" button at the bottom of the screen in a UIToolBar ? When the user taps the button, you show row 21-40, etc, etc. You can also add a "Previous" button to go backwards.

- (void)setUpToolbar
{
// add a toolbar with a prev and next button
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: @""
                                                                         style: UIBarButtonItemStylePlain
                                                                        target: nil
                                                                        action: nil];

UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemFlexibleSpace
                                                                              target: nil
                                                                              action: nil];

self.prevButton = [[UIBarButtonItem alloc] initWithTitle: NSLocalizedString(@"Prev", nil)
                                                   style: UIBarButtonItemStylePlain
                                                  target: self
                                                  action: @selector(clickedPrevButton:)];

self.nextButton = [[UIBarButtonItem alloc] initWithTitle: NSLocalizedString(@"Next", nil)
                                                   style: UIBarButtonItemStylePlain
                                                  target: self
                                                  action: @selector(clickedNextButton:)];

self.nextButton.enabled = NO;
self.prevButton.enabled = NO;
self.page = 1;

self.toolbarItems = @[self.prevButton, flexibleItem, self.nextButton];
}

- (void) clickedNextButton: (id) sender
{    
if ([self.nextButton.title isEqualToString: NSLocalizedString(@"More Results", nil)])
{
    self.offset += kSearchLimit;
    self.nextButton.title = NSLocalizedString(@"Next", nil);
    self.page += 1;

    [self searchDatabase];
}
else
{
    self.page += 1;

    if ((self.page - 1) * kEntriesToDisplay > self.searchResults.count)
    {
        self.nextButton.enabled = NO;
    }

    if (self.page * kEntriesToDisplay == self.searchResults.count)
    {
        self.nextButton.enabled = YES;
        self.nextButton.title = NSLocalizedString(@"More Results", nil);
    }

    self.prevButton.enabled = YES;

    [self updateSearchUI];        
}
}

- (void) clickedPrevButton: (id) sender
{
self.page -= 1;

if (self.page == 1)
    self.prevButton.enabled = NO;

self.nextButton.title = NSLocalizedString(@"Next", nil);
self.nextButton.enabled = YES;

[self updateSearchUI];
}
koen
  • 5,383
  • 7
  • 50
  • 89
  • thats sounds like a plan as well. how should I got about in achieving that? – H. Lamb Feb 23 '16 at 15:03
  • I added some sample code that I use in a project. Sorry, it is in `ObjectiveC`, but hopefully you can use it as a starting point. – koen Feb 23 '16 at 16:06
0

This is correct way to handle load more in UITableView. To avoid fluctuation, below method called when scroll view grinds to a halt.

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let offsetY = scrollView.contentOffset.y
    let scrollHeight = scrollView.frame.size.height

    let endScrolling = offsetY + scrollHeight

    if endScrolling >= scrollView.contentSize.height {
        //Load more logic
    }
}
Sunil Targe
  • 7,251
  • 5
  • 49
  • 80