90

Is there a way to know if a UIScrollView has reached the top or bottom? Possibly in the method:

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
                  willDecelerate:(BOOL)decelerate
pkamb
  • 33,281
  • 23
  • 160
  • 191
Duck
  • 34,902
  • 47
  • 248
  • 470

6 Answers6

201

Implement the UIScrollViewDelegate in your class, and then add this:

-(void)scrollViewDidScroll: (UIScrollView*)scrollView
{
    float scrollViewHeight = scrollView.frame.size.height;
    float scrollContentSizeHeight = scrollView.contentSize.height;
    float scrollOffset = scrollView.contentOffset.y;

    if (scrollOffset == 0)
    {
        // then we are at the top
    }
    else if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
    {
        // then we are at the end
    }
}

Hope this is what you are after! Else have a tinker by adding more conditions to the above code and NSLog the value of scrollOffset.

Mundi
  • 79,884
  • 17
  • 117
  • 140
Luke
  • 11,426
  • 43
  • 60
  • 69
  • 5
    If you're in a `navigationController`, you might want to do something more like this: `scrollOffset <= (0 - self.navigationController.navigationBar.frame.size.height - 20)` and `(scrollOffset + scrollHeight) >= scrollContentSizeHeight` – Wayne Jan 09 '14 at 22:02
  • Problem with that is the bounce...everything gets called twice once reaches top or bottom. Is there a solution to that, besides turning off table or collection view bounce? – denikov Aug 29 '14 at 17:04
  • @denikov did you find any solution for that? – Priyal Jun 07 '17 at 06:57
  • scrollOffset may be <0 if scrollview top constraint != 0 – famfamfam Mar 02 '21 at 07:59
88

Well, contentInsets are also involved, when you try to determine whether scrollView is at the top or at the bottom. You might also be interested in cases when your scrollView is above the top and below the bottom. Here is the code I use to find top and bottom positions:

Swift:

extension UIScrollView {

    var isAtTop: Bool {
        return contentOffset.y <= verticalOffsetForTop
    }

    var isAtBottom: Bool {
        return contentOffset.y >= verticalOffsetForBottom
    }

    var verticalOffsetForTop: CGFloat {
        let topInset = contentInset.top
        return -topInset
    }

    var verticalOffsetForBottom: CGFloat {
        let scrollViewHeight = bounds.height
        let scrollContentSizeHeight = contentSize.height
        let bottomInset = contentInset.bottom
        let scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight
        return scrollViewBottomOffset
    }

}

Objective-C:

@implementation UIScrollView (Additions)

- (BOOL)isAtTop {
    return (self.contentOffset.y <= [self verticalOffsetForTop]);
}

- (BOOL)isAtBottom {
    return (self.contentOffset.y >= [self verticalOffsetForBottom]);
}

- (CGFloat)verticalOffsetForTop {
    CGFloat topInset = self.contentInset.top;
    return -topInset;
}

- (CGFloat)verticalOffsetForBottom {
    CGFloat scrollViewHeight = self.bounds.size.height;
    CGFloat scrollContentSizeHeight = self.contentSize.height;
    CGFloat bottomInset = self.contentInset.bottom;
    CGFloat scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight;
    return scrollViewBottomOffset;
}


@end
Alexander Dvornikov
  • 1,074
  • 8
  • 13
  • This the good answer, implementing scrollviewdidscroll is bad for performences – Vassily Dec 18 '14 at 13:56
  • 3
    How do I call it if not using scrollViewDidScroll? – Dave G Jul 14 '16 at 23:25
  • 3
    Because of the vagaries of floating point math, I was getting a "verticalOffsetForBottom" of 1879.05xxx and my contentOffset.y was 1879 when at the bottom in Swift 3. So I changed `return scrollViewBottomOffset` to `return scrollViewBottomOffset.rounded()` – chadbag May 18 '17 at 12:25
  • 1
    As of iOS 11 `safeAreaInsets` also factor into it. `scrollView.contentOffset.y + scrollView.bounds.height - scrollView.safeAreaInsets.bottom` – InkGolem Jul 03 '18 at 23:17
  • 1
    In iOS 11 and above you may need to use `adjustedContentInset` instead of `contentInset`. – nemissm Feb 16 '22 at 23:57
41

If you want the code in swift:

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
    }
}
MBH
  • 16,271
  • 19
  • 99
  • 149
ytbryan
  • 2,644
  • 31
  • 49
13

Simple and clean extension:

import UIKit

extension UIScrollView {

    var scrolledToTop: Bool {
        let topEdge = 0 - contentInset.top
        return contentOffset.y <= topEdge
    }

    var scrolledToBottom: Bool {
        let bottomEdge = contentSize.height + contentInset.bottom - bounds.height
        return contentOffset.y >= bottomEdge
    }

}

On GitHub:

https://github.com/salutis/swift-scroll-view-edges

Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
5

I figured out exactly how to do it:

CGFloat maxPosition = scrollView.contentInset.top + scrollView.contentSize.height + scrollView.contentInset.bottom - scrollView.bounds.size.height;
CGFloat currentPosition = scrollView.contentOffset.y + self.topLayoutGuide.length;

if (currentPosition == maxPosition) {
  // you're at the bottom!
}
JPN
  • 632
  • 12
  • 24
0

Do it like this(for Swift 3):

class MyClass: UIScrollViewDelegate {

   func scrollViewDidScroll(_ scrollView: UIScrollView) {

        if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
           //scrolled to top, do smth
        }


    }
}
  • Welcome to SO! When posting code in your answer, it is helpful o explain how your code solves the OP's problem :) – Joel Sep 11 '18 at 18:12