Check if a UIScrollView reached the top or bottom
Asked Answered
E

6

91

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
Espinosa answered 9/10, 2011 at 19:56 Comment(0)
B
202

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.

Benisch answered 9/10, 2011 at 20:1 Comment(3)
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) >= scrollContentSizeHeightAshes
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?Boethius
scrollOffset may be <0 if scrollview top constraint != 0Starobin
T
89

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
Tenishatenn answered 7/2, 2013 at 7:31 Comment(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()Known
As of iOS 11 safeAreaInsets also factor into it. scrollView.contentOffset.y + scrollView.bounds.height - scrollView.safeAreaInsets.bottomSelmore
In iOS 11 and above you may need to use adjustedContentInset instead of contentInset.Subservience
P
42

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
    }
}
Plasmagel answered 1/10, 2014 at 13:13 Comment(0)
M
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

Mountebank answered 6/11, 2015 at 14:14 Comment(0)
A
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!
}
Action answered 14/4, 2014 at 18:57 Comment(0)
R
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
        }


    }
}
Robomb answered 11/9, 2018 at 17:55 Comment(1)
Welcome to SO! When posting code in your answer, it is helpful o explain how your code solves the OP's problem :)Creamy

© 2022 - 2024 — McMap. All rights reserved.