44

I know the Apple documentation has the following delegate method:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;      // called when scroll view grinds to a halt

However, it doesn't necessarily mean you are at the bottom. Cause if you use your finger, scroll a bit, then it decelerates, but you are not actually at the bottom of your scroll view, then it still gets called. I basically want an arrow to show that there is more data in my scroll view, and then disappear when you are at the bottom (like when it bounces). Thanks.

Crystal
  • 28,460
  • 62
  • 219
  • 393

7 Answers7

105

I think what you might be able to do is to check that your contentOffset point is at the bottom of contentSize. So you could probably do something like:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    float bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height;
    if (bottomEdge >= scrollView.contentSize.height) {
        // we are at the end
    }
}

You'll likely also need a negative case there to show your indicator when the user scrolls back up. You might also want to add some padding to that so, for example, you could hide the indicator when the user is near the bottom, but not exactly at the bottom.

bensnider
  • 3,742
  • 1
  • 24
  • 25
  • 11
    Best solution but I would put it in - (void)scrollViewDidScroll:(UIScrollView *)scrollView{} instead – MobileMon Jan 07 '13 at 17:27
  • @MobileMon is right it should be used in `scrollViewDidScroll` method. – Manan Devani May 06 '16 at 09:10
  • Just the lines I was looking for. Works with UICollectionView too. – Ahmed Elashker Oct 03 '16 at 20:09
  • If you are willing to put your logic inside `scrollViewDidScroll`, you dave to design some guarding flag which will prevent your logic from firing million times. This is because you have `>=` there and it will successfully get inside the `if` clause million of times, when your table view bounces. The end of deceleration is much better event to observe since it gets called after bouncing had finished – efimovdk Apr 10 '19 at 10:06
  • scrollViewDidEndDecelerating not triggered if i call : cv.scrollToBottom() – famfamfam Dec 12 '21 at 06:16
25

So if you want it in swift, here you go:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
        //reach bottom
    }

    if (scrollView.contentOffset.y < 0){
        //reach top
    }

    if (scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y < (scrollView.contentSize.height - scrollView.frame.size.height)){
        //not top and not bottom
    }
}
byJeevan
  • 3,728
  • 3
  • 37
  • 60
ytbryan
  • 2,644
  • 31
  • 49
14

I Think @bensnider answer is correct, But not exart. Because of these two reasons

1. - (void)scrollViewDidScroll:(UIScrollView *)scrollView{}

This method will call continuously if we check for if (bottomEdge >= scrollView.contentSize.height)

2 . In this if we go for == check also this condition will valid for two times.

  • (i) when we will scroll up when the end of the scroll view touches the bottom edge
  • (ii) When the scrollview bounces back to retain it's own position

I feel this is more accurate.

Very few cases this codition is valid for two times also. But User will not come across this.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
 {
    if (scrollView.contentOffset.y == roundf(scrollView.contentSize.height-scrollView.frame.size.height)) {
    NSLog(@"we are at the endddd");

   //Call your function here...

    }
}
umakanta
  • 1,051
  • 19
  • 25
  • 2
    Yes, this should've been the accepted answer. Also, I think the contentScale factor should be used in the equation – luksfarris Feb 08 '16 at 11:49
10

The accepted answer works only if the bottom contentInset value is non-negative. A slight evolution would consider the bottom of the contentInset regardless of it's sign:

CGFloat bottomInset = scrollView.contentInset.bottom;
CGFloat bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height - bottomInset;
if (bottomEdge == scrollView.contentSize.height) {
    // Scroll view is scrolled to bottom
}
kadam
  • 1,259
  • 3
  • 14
  • 27
3

Actually, rather than just putting @bensnider's code in scrollViewDidScroll, this code (written in Swift 3) would be better performance-wise:

func scrollViewDidEndDragging(_ scrollView: UIScrollView,
                              willDecelerate decelerate: Bool) {
    if !decelerate {
        checkHasScrolledToBottom()
    }
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    checkHasScrolledToBottom()
}

func checkHasScrolledToBottom() {
    let bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height
    if bottomEdge >= scrollView.contentSize.height {
        // we are at the end
    }
}
Ivan
  • 497
  • 4
  • 11
2

It work in Swift 3:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y == (scrollView.contentSize.height - scrollView.frame.size.height) {
        loadMore()
    }
}
Janserik
  • 2,306
  • 1
  • 24
  • 43
0

See what items are currently displayed in the UIView, using something like indexPathsForVisibleRows and if your model has more items than displayed, put an arrow at the bottom.

Abizern
  • 146,289
  • 39
  • 203
  • 257