101

I would like to know when a UITableView did scroll to bottom in order to load and show more content, something like a delegate or something else to let the controller know when the table did scroll to bottom.

How can I do this?

Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48
Son Nguyen
  • 3,481
  • 4
  • 33
  • 47

17 Answers17

247

in the tableview delegate do something like this

ObjC:

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;
    CGSize size = aScrollView.contentSize;
    UIEdgeInsets inset = aScrollView.contentInset;
    float y = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;
    // NSLog(@"offset: %f", offset.y);   
    // NSLog(@"content.height: %f", size.height);   
    // NSLog(@"bounds.height: %f", bounds.size.height);   
    // NSLog(@"inset.top: %f", inset.top);   
    // NSLog(@"inset.bottom: %f", inset.bottom);   
    // NSLog(@"pos: %f of %f", y, h);

    float reload_distance = 10;
    if(y > h + reload_distance) {
        NSLog(@"load more rows");
    }
}

Swift:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let inset = scrollView.contentInset
    let y = offset.y + bounds.size.height - inset.bottom
    let h = size.height
    let reload_distance:CGFloat = 10.0
    if y > (h + reload_distance) {
        print("load more rows")
    }
}
neoneye
  • 50,398
  • 25
  • 166
  • 151
  • Good! Just use: y >= h + reload_distance, which is really only a concern when reload_distance = 0 (e.g., for bounces NO). – Chris Prince Jun 20 '14 at 18:47
  • 4
    This code in "scrollViewDidScroll" being executed all the time user moves his finger up/down on screen. It is better to move it under "scrollViewDidEndDecelerating". This way it will be executed once when user stops moving his finger. – Misha Feb 18 '15 at 09:38
  • hmm.. this code still works? not getting the bottom of UITableView for me. I wish i knew the reasoning behind all the code above so maybe i can fix – Just a coder Jan 26 '16 at 11:32
  • offset: 237.000000 content.height: 855.000000 bounds.height: 667.000000 inset.top: 64.000000 inset.bottom: 49.000000 pos: 855.000000 of 855.000000 if is getting false – Abhishek Thapliyal May 20 '16 at 13:41
62

Modified neoneyes answer a bit.

This answer targets those of you who only wants the event to be triggered once per release of the finger.

Suitable when loading more content from some content provider (web service, core data etc). Note that this approach does not respect the response time from your web service.

- (void)scrollViewDidEndDragging:(UIScrollView *)aScrollView
                  willDecelerate:(BOOL)decelerate
{
    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;
    CGSize size = aScrollView.contentSize;
    UIEdgeInsets inset = aScrollView.contentInset;
    float y = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;

    float reload_distance = 50;
    if(y > h + reload_distance) {
        NSLog(@"load more rows");
    }
}
Eyeball
  • 3,267
  • 2
  • 26
  • 50
  • This doesn't work. Whenever the user scrolls to bottom, the "load more rows" gets called. This happens even when user has already scrolled to bottom and are waiting for the API to return data. – netwire Jul 14 '14 at 06:45
  • 2
    Dean, the question was about knowing when the table view scrolled to bottom. Wether you are fetching data from an API or not is irrelevant, isnt it? – Eyeball Jul 14 '14 at 08:49
  • But there is no message showing load more.. so that user can be aware that there is more results to show. – iPhone 7 Mar 11 '16 at 06:55
  • Nice answer in my case it is working if : float reload_distance = 10; if(y > (h - reload_distance)) { NSLog(@"load more rows"); } because y = 565 and h is also 565 – Abhishek Thapliyal May 20 '16 at 12:43
  • 1
    I tried both Eyeball 's and @neoneye 's answer .Believe me or not 'scrollViewDidScroll' called multiple times compared to 'scrollViewDidEndDragging'. So It was efficient to use 'scrollViewDidEndDragging' as i have API call on it. – Vinod Supnekar Aug 10 '18 at 05:28
39

add this method in the UITableViewDelegate:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    CGFloat height = scrollView.frame.size.height;

    CGFloat contentYoffset = scrollView.contentOffset.y;

    CGFloat distanceFromBottom = scrollView.contentSize.height - contentYoffset;

    if(distanceFromBottom < height) 
    {
        NSLog(@"end of the table");
    }
}
Venk
  • 5,949
  • 9
  • 41
  • 52
8suhas
  • 1,460
  • 11
  • 20
  • 3
    I like this solution except one small detail. if(distanceFromBottom <= height) – Mark McCorkle Mar 14 '13 at 19:40
  • This helped me troubleshoot my issue, thank you!! a tab bar!! it was a stupid tab bar!! – Dmytro May 17 '16 at 04:40
  • this work like a charm, and it is simple too, but i am facing one small issue like it is executing two or three times at the last row. how to make it execute only one time when it reach the last row of tableview ? – R. Mohan Jul 28 '17 at 11:11
  • Thank you so much!! I put this code in scrollViewWillBeginDecelerating and it executes only once when scrolled to last row. – Manali Dec 21 '17 at 11:56
20

The best way is to test a point at the bottom of the screen and use this method call when ever the user scrolls (scrollViewDidScroll):

- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point

Test a point near the bottom of the screen, and then using the indexPath it returns check if that indexPath is the last row then if it is, add rows.

Venk
  • 5,949
  • 9
  • 41
  • 52
Ajay
  • 2,078
  • 1
  • 15
  • 13
  • Unfortunately that my App will update data real time for existing rows in this table, so this way may get more content when the table is not scroll at all. – Son Nguyen Feb 28 '11 at 03:58
20

None of the answers above helped me, so I came up with this:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{   
    NSArray *visibleRows = [self.tableView visibleCells];
    UITableViewCell *lastVisibleCell = [visibleRows lastObject];
    NSIndexPath *path = [self.tableView indexPathForCell:lastVisibleCell];
    if(path.section == lastSection && path.row == lastRow)
    {
        // Do something here
    }
}
Iftach Orr
  • 201
  • 2
  • 2
19

Use – tableView:willDisplayCell:forRowAtIndexPath: (UITableViewDelegate method)

Simply compare the indexPath with the items in your data array (or whatever data source you use for your table view) to figure out if the last element is being displayed.

Docs: http://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:willDisplayCell:forRowAtIndexPath:

Venk
  • 5,949
  • 9
  • 41
  • 52
Wolfgang Schreurs
  • 11,779
  • 7
  • 51
  • 92
14

UITableView is a subclass of UIScrollView, and UITableViewDelegate conforms to UIScrollViewDelegate. So the delegate you attach to the table view will get events such as scrollViewDidScroll:, and you can call methods such as contentOffset on the table view to find the scroll position.

pablasso
  • 2,479
  • 2
  • 26
  • 32
Anomie
  • 92,546
  • 13
  • 126
  • 145
  • Yes, that is what i am looking for, by implement that delegate and check if scroll position is UITableViewScrollPositionBottom, I will know when table is scroll to bottom, thanks so much – Son Nguyen Feb 28 '11 at 04:07
8
NSLog(@"%f / %f",tableView.contentOffset.y, tableView.contentSize.height - tableView.frame.size.height);

if (tableView.contentOffset.y == tableView.contentSize.height - tableView.frame.size.height)
        [self doSomething];

Nice and simple

user1641587
  • 121
  • 2
  • 2
8

in Swift you can do something like this. Following condition will be true every time you reach end of the tableview

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row+1 == postArray.count {
            println("came to last row")
        }
}
Jay Mayu
  • 17,023
  • 32
  • 114
  • 148
  • 1
    the problem is when tableview has only 3 cells for example the `println("came to last row")` this code runs ! – iOS.Lover Nov 04 '17 at 15:12
  • @Mc.Lover that was the exact problem for me for which CPU usage was going to 92%. neoneye 's answer worked for me. I also added swift version for it if you need. – Anuran Barman Feb 13 '19 at 10:27
7

Building on @Jay Mayu's answer, which I felt was one of the better solutions:

Objective-C

// UITableViewDelegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

// Need to call the service & update the array
if(indexPath.row + 1 == self.sourceArray.count) {
    DLog(@"Displayed the last row!");
  }
}

Swift 2.x

// UITableViewDelegate
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    if (indexPath.row + 1) == sourceArray.count {
        print("Displayed the last row!")
    }
}
footyapps27
  • 3,982
  • 2
  • 25
  • 42
6

Here is the swift 3.0 version code.

   func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let offset = scrollView.contentOffset
        let bounds = scrollView.bounds
        let size = scrollView.contentSize
        let inset = scrollView.contentInset
        let y: Float = Float(offset.y) + Float(bounds.size.height) + Float(inset.bottom)
        let height: Float = Float(size.height)
        let distance: Float = 10

        if y > height + distance {
            // load more data
        }
    }
James Kuang
  • 10,710
  • 4
  • 28
  • 38
Morshed Alam
  • 153
  • 2
  • 10
5

I generally use this to load more data , when last cell starts display

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   if (indexPath.row ==  myDataArray.count-1) {
        NSLog(@"load more");
    }
}
Shaik Riyaz
  • 11,204
  • 7
  • 53
  • 70
5

Taking neoneye excellent answers but in swift, and renaming of the variables..

Basically we know we have reached the bottom of the table view if the yOffsetAtBottom is beyond the table content height.

func isTableViewScrolledToBottom() -> Bool {
    let tableHeight = tableView.bounds.size.height
    let contentHeight = tableView.contentSize.height
    let insetHeight = tableView.contentInset.bottom

    let yOffset = tableView.contentOffset.y
    let yOffsetAtBottom = yOffset + tableHeight - insetHeight

    return yOffsetAtBottom > contentHeight
}
Community
  • 1
  • 1
samwize
  • 25,675
  • 15
  • 141
  • 186
3

My solution is to add cells before tableview will decelerate on estimated offset. It's predictable on by velocity.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)offset {

    NSLog(@"offset: %f", offset->y+scrollView.frame.size.height);
    NSLog(@"Scroll view content size: %f", scrollView.contentSize.height);

    if (offset->y+scrollView.frame.size.height > scrollView.contentSize.height - 300) {
        NSLog(@"Load new rows when reaches 300px before end of content");
        [[DataManager shared] fetchRecordsNextPage];
    }
}
fir
  • 387
  • 1
  • 3
  • 19
3

Update for Swift 3

Neoneye's answer worked best for me in Objective C, this is the equivalent of the answer in Swift 3:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let offset: CGPoint = scrollView.contentOffset
    let bounds: CGRect = scrollView.bounds
    let size: CGSize = scrollView.contentSize
    let inset: UIEdgeInsets = scrollView.contentInset
    let y: CGFloat = offset.y + bounds.size.height - inset.bottom
    let h: CGFloat = size.height
//        print("offset: %f", offset.y)
//        print("content.height: %f", size.height)
//        print("bounds.height: %f", bounds.size.height)
//        print("inset.top: %f", inset.top)
//        print("inset.bottom: %f", inset.bottom)
//        print("position: %f of %f", y, h)

    let reloadDistance: CGFloat = 10

    if (y > h + reloadDistance) {
            print("load more rows")
    }
}
Nike Kov
  • 12,630
  • 8
  • 75
  • 122
Ibrahim
  • 6,006
  • 3
  • 39
  • 50
2

I want perform some action on my any 1 full Tableviewcell.

So the code is link the :

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSArray* cells = self.tableView.visibleCells;
    NSIndexPath *indexPath = nil ;

    for (int aIntCount = 0; aIntCount < [cells count]; aIntCount++)
    {

        UITableViewCell *cell = [cells objectAtIndex:aIntCount];
CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.tableView.superview];

        if (CGRectContainsRect(self.tableView.frame, cellRect))
        {
            indexPath = [self.tableView indexPathForCell:cell];

    //  remain logic
        }
     }
}

May this is help to some one.

beryllium
  • 29,669
  • 15
  • 106
  • 125
Mayuri R Talaviya
  • 613
  • 1
  • 9
  • 14
0

@neoneye's answer worked for me. Here is the Swift 4 version of it

    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let insets = scrollView.contentInset
    let y = offset.y + bounds.size.height - insets.bottom
    let h = size.height
    let reloadDistance = CGFloat(10)
    if y > h + reloadDistance {
        //load more rows
    }
Anuran Barman
  • 1,556
  • 2
  • 16
  • 31