30

Is there a way to trigger an event, such as with an IBAction, when a user scrolls to the bottom of a UITableView? I would like to add more rows if this happens. How do I do this?

jscs
  • 63,694
  • 13
  • 151
  • 195
aherlambang
  • 14,290
  • 50
  • 150
  • 253
  • possible duplicate of [\[iPhone\] How to know when UITableView did scroll to bottom](http://stackoverflow.com/questions/5137943/iphone-how-to-know-when-uitableview-did-scroll-to-bottom) – jscs May 16 '11 at 21:45
  • Please search before posting. Searching on your title reveals this question I have linked, which has the same information as the answer which Henri has given you. – jscs May 16 '11 at 22:50
  • 2
    Here is a demo of infinite UITableView, using Swift: https://github.com/i-schuetz/tableview_infinite it loads data using async task and shows a loading footer. – User Jun 27 '14 at 19:45

7 Answers7

57

Unless it´s a little late, but i think i found a better solution:

instead of

 - (void)scrollViewDidScroll: (UIScrollView)scroll

i used

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

This is much more convenient, cause the event is only triggered once. I used this code in my application to load more rows in my tableview at the bottom (maybe you recognize this type of reloading from the facebook application - only difference that they are updating at the top).

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {    
    NSInteger currentOffset = scrollView.contentOffset.y;
    NSInteger maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    if (maximumOffset - currentOffset <= -40) {
        NSLog(@"reload");
    }
}

Hope anyone will help this.

niaher
  • 9,460
  • 7
  • 67
  • 86
user944351
  • 1,213
  • 2
  • 19
  • 27
  • This is what I needed to only make a single call to my server. `- (void)scrollViewDidScroll:` is triggered to many times to make a call to your server. Unless you do as @Henri Normak suggested above in his comments and add a check for updating. This is much simpler. Thanks @user944351 – Ben Nov 26 '13 at 03:46
  • I tested this but it only works when the drag starts when the table view is already near to the bottom. If it's a long scroll, the if condition is not met even if it ends exactly at the bottom. – User Jun 27 '14 at 14:28
  • 1
    Makes sense since the method is called "end dragging" so I think is called with the offset when the user ends the drag gesture. But the list can continue scrolling. And it should load automatically when it reaches the bottom, so it looks like `scrollViewDidScroll` is correct. – User Jun 27 '14 at 14:34
  • Same code in swift 3 let currentOffset: Int = Int(scrollView.contentOffset.y) let maximumOffset = Int(scrollView.contentSize.height - scrollView.frame.size.height) if maximumOffset - currentOffset <= -40 { print("got") } – iOS Lifee Sep 29 '17 at 06:54
38

Simply listen to the scrollViewDidScroll: delegate method, compare the content offset with the current possible offset and if lower then some threshold call your method to update the tableview. Don't forget to call [tableView reloadData] to make sure it reload the newly added data.

EDIT: Put together abstract code, not sure if it works, but should.

- (void)scrollViewDidScroll: (UIScrollView *)scroll {
     // UITableView only moves in one direction, y axis
     CGFloat currentOffset = scroll.contentOffset.y;
     CGFloat maximumOffset = scroll.contentSize.height - scroll.frame.size.height;

     // Change 10.0 to adjust the distance from bottom
     if (maximumOffset - currentOffset <= 10.0) {
          [self methodThatAddsDataAndReloadsTableView];
     }
}
Rajan Balana
  • 3,775
  • 25
  • 42
Henri Normak
  • 4,695
  • 2
  • 23
  • 33
  • Don't have a project atm to quickly show something in. I presume your viewController is the delegate of the tableView. If so, just add scrollViewDidScroll: to the viewController and use that as the place to trigger the update method. Comparing the contentOffset.y with maximum (contenSize.y - tableView.frame.size.height) should be quite straightforward. – Henri Normak May 16 '11 at 21:45
  • The key is to realize that UITableView is a subclass of UIScrollView and it passes on the delegate methods as well. – Henri Normak May 16 '11 at 21:46
  • and will this be triggered once the scrolling is stopped or will it also execute while scrolling? – aherlambang May 16 '11 at 21:55
  • 2
    I believe this is triggered every single time the contentOffset changes, meaning if you scroll, this will get triggered a lot. Which reminds me, if you are doing a request from the web, you should probably disable this system for the time being when the request is ongoing and reenable once content has been added. Just add a boolean somewhere isUpdating and check that along with the offset difference. – Henri Normak May 16 '11 at 21:59
  • why is it also firing the method [self methodThatAddsDataAndReloadsTableView]; – aherlambang May 16 '11 at 22:09
  • What do you mean by that? I added that line just so you know where to put all your updating stuff into, you started the question with a desire to trigger an event when the scrollView was at the bottom, that event should go instead of the [self methodThatAddsDataAndReloadsTableView]; line. – Henri Normak May 16 '11 at 22:14
13

I use this snippet. in tableView:willDisplayCell:forRowAtIndexPath: I check, if the cell with the last index path is about to be diplayed.

For a tableView with one section:

[indexPath isEqual:[NSIndexPath indexPathForRow:[self tableView:self.tableView numberOfRowsInSection:0]-1 inSection:0]

with more sections:

[indexPath isEqual:[NSIndexPath indexPathForRow:[self tableView:self.tableView numberOfRowsInSection:0]-1 inSection:[self numberOfSectionsInTableView:self.tableView]-1]

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(!_noMoreDataAvailable)
    {
        if ([indexPath isEqual:[NSIndexPath indexPathForRow:[self tableView:self.tableView numberOfRowsInSection:0]-1 inSection:0]])
        {
            [self.dataSourceController fetchNewData];
        }
    }
}

after fetching the dataSourceController will inform the tableView delegate and this will reload the data.

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • 2
    Brilliant answer, much prefer this compared scrollViewDidScroll option - thanks vikingosegundo! – Rob Jan 16 '14 at 14:00
  • If you use loading or error cells, be sure to have a small check like: if(indexPath.row > 5) or simple set the boolean noMoreDataAvailble to false. – Sjoerd Perfors Apr 18 '14 at 14:12
  • Not great if you want to load the data a little way before the user gets to the last cell. I find it 'flows' better when you load it a few cells before – Robert J. Clegg Apr 30 '15 at 11:01
  • @Tander, you should be able to change that to an earlier indexPath. – vikingosegundo Apr 30 '15 at 11:59
1
NSInteger currentOffset = scrollView.contentOffset.y;
NSInteger maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

// Hit Bottom?
if ((currentOffset > 0) && (maximumOffset - currentOffset) <= 10) {
   // Hit Bottom !! 
}
bi Park
  • 11
  • 2
1

It can be achieved in much simpler way I guess, you just need to determine the scrolling direction as in this case it is when the user is scrolling downwards.

First in your .h file declare a variable :

CGPoint pointNow;

In .m file, in scrollViewDidScroll method we need to implement this code:-

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView.contentOffset.y > pointNow.y) {
        //Enter code here
    }
}
Essan Parto
  • 703
  • 11
  • 18
Jas_meet
  • 366
  • 1
  • 20
1

Swift

Here is the Swift version of @user944351's answer:

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
    let currentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
    
    if maximumOffset - currentOffset <= -40 {
        print("reload")
    }
}

Just add this method to your UITableView delegate class. I found -40 too large but you can adjust it according to your needs.

More info

  • See my fuller answer that has an example of method to reload data using limits and offsets for the database.
Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
0

I'm not sure why you'd want to do this, but I imagine you could add some code to your cellForRowAtIndexPath method so that if indexPath.row == the last row, call the method to add more rows...

Venk
  • 5,949
  • 9
  • 41
  • 52
rekle
  • 2,375
  • 1
  • 18
  • 9
  • 4
    because I would like to call a REST web service to add more rows as there are more entries that can be seen by the user, instead of having a "load more posts" kind of thing, why not do it automatically – aherlambang May 16 '11 at 21:42