8

I use UICollectionView + AFNetworking 2 for loading images asynchronously from a webservice. It's working great. However, how do I implement automatic scrolling (when user reaches bottom of page, or close to it, the API is called to fetch more cells).

I've seen lots of examples but there doesn't seem to be a best practice. For example, one tutorial suggested scrollViewDidScroll, but that's called every time the view is scrolled. Is that the correct implementation?

PS. I'm coding in Swift.

Here's my cell creation code:

func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell! {

    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as UICollectionViewCell

    let offer = self.offers[indexPath.row]
    var cellImageView = cell.viewWithTag(100) as UIImageView
    let requestURL = NSURLRequest(URL: offer.largeImageURL)
    cellImageView.setImageWithURLRequest(
        requestURL,
        placeholderImage:placeholderImage,
        success: {
            (request:NSURLRequest!, response:NSHTTPURLResponse!, image:UIImage!) in
            cellImageView.image = image
        },
        failure: {
            (request:NSURLRequest!, response:NSHTTPURLResponse!, error:NSError!) in
            NSLog("GET Image Error: " + error.localizedDescription)
        }
    )
    return cell
}
Shruti Thombre
  • 989
  • 4
  • 11
  • 27
netwire
  • 7,108
  • 12
  • 52
  • 86
  • I posted this a while back. Its not Swift, but maybe it is what you are trying to achieve. http://stackoverflow.com/questions/5137943/how-to-know-when-uitableview-did-scroll-to-bottom-in-iphone/17860149?noredirect=1#comment38362893_17860149 – Eyeball Jul 19 '14 at 14:48
  • Thanks Eyeball. I am able to detect when user scrolls to bottom of screen, but it gets detected every time bottom of screen is reached, which can happen multiple times before the web service returns any results, and thus calling the web service again. I'm interested in a more comprehensive solution, including how to track pagination offset in app, when to call afnetworking, how to indicate screen is reloading. – netwire Jul 19 '14 at 14:58
  • I would do it in `-scrollViewDidScroll:`. Even if this is called at every frame while scrolling, performance shouldn't really be a problem. Just check if you're close to the bottom (compare offset to content height) and load new images if needed. – Christian Schnorr Jul 19 '14 at 16:40

4 Answers4

3

You could do it in a scrollViewDidScroll delegate method or collectionView:cellForItemAtIndexPath: (here you could detect that last cell is loading). What you would need to do is to create a wrapper for AFNetworking call to prevent it from loading the data few times (some kind of flag that indicates that it's already loading).

After that you could insert additional cell with some kind of activity indicator at the bottom and remove it after AFNetworking call is finished.

blazejmar
  • 1,080
  • 8
  • 10
  • Thanks blazejmar, interesting suggestion. Do you keep a variable in the ViewController keeping track of the offset for pagination? I'll have to look into how to insert cell that looks good with UICollectionView, not UITableView. Let me try this approach. – netwire Jul 19 '14 at 16:23
  • It really depends on how your API works. If you have to pass offset and page size than you would need to store it somewhere. VC seems a good place. If your API takes id of the last object you have than you have it already so you don't need to create any additional variable to store it. – blazejmar Jul 19 '14 at 16:26
  • The collectionView function is called by reloadData to refresh the view. How do I limit the refresh to only the new conte you, not all the content? – netwire Jul 19 '14 at 20:33
  • Are you using CoreData? If yes then you can NSFetchedResultsController and use its delegate method callbacks to gather all inserted/deleted/updated objects. If not you must track objects you've changed. Either way in the end you can call performBatchUpdates with: - insertItemsAtIndexPaths: – moveItemAtIndexPath:toIndexPath: – deleteItemsAtIndexPaths: inside. – blazejmar Jul 19 '14 at 20:38
1

You should use the willEndDragging UIScrollview delegate method to determine where the scrollview will stop scrolling. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

This has several advantages over the other methods:

  1. didScroll will be called for every time the scrollview scrolls, even fractionally, and even when you trigger it from code. I've seen didScroll get called even with a bounds change, like when the orientation changes, especially on iPad. It just gets called too often!
  2. didEndDecelerating may not get called if the scrollview comes to a sudden stop - like when the user presses and hold the scrollview while it's slowing
  3. willEndDragging will only be called when the user scrolls your scroll view, not by code / layout changes.

In the willEndDragging, use the target content offset to determine if you need to load more results. Once your API has finished fetching data, simply call reloadData on the collection view and the new results will be shown. Note that the target contentOffset is a pointer, so you'll have to use targetContentOffset->y to get the target y position.

The actual code to do this will be implementation dependent so I'm not putting in any sample, but it should be simple enough to code up.

Dhiraj Gupta
  • 9,704
  • 8
  • 49
  • 54
0

Yes, use scrollViewDidScroll to check that you are at the bottom, than do any work you need (place loader, load data and so on).

You can use ready component SVInfiniteScrolling (which is part of SVPullToRefresh). Or look at its implementation to be inspired.

halfer
  • 19,824
  • 17
  • 99
  • 186
Anton Gaenko
  • 8,929
  • 6
  • 44
  • 39
0

I'm using this code in my project. I wrote it with Obj-C and don't know enough to convert it to Swift. So, if you can convert, you can detect yourself if user reach end of the page like that;

- (BOOL)detectEndofScroll
{
    BOOL scrollResult = NO;
    CGPoint offset = _collectionView.contentOffset;
    CGRect bounds = _collectionView.bounds;
    CGSize size = _collectionView.contentSize;
    UIEdgeInsets inset = _collectionView.contentInset;
    float yAxis = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;
    if(yAxis+14 > h)
    {
        scrollResult = YES;
    }
    else
    {
        scrollResult = NO;
    }
    return scrollResult;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    if ([self detectEndofScroll])
    {
        // load more data
    }
}
Kemal Can Kaynak
  • 1,638
  • 14
  • 26