ios - UIScrollView scroll to bottom programmatically


Translate

How can I make a UIScrollView scroll to the bottom within my code? Or in a more generic way, to any point of a subview?



All Answers
  • Translate

    You can use the UIScrollView's setContentOffset:animated: function to scroll to any part of the content view. Here's some code that would scroll to the bottom, assuming your scrollView is self.scrollView:

    CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
    [self.scrollView setContentOffset:bottomOffset animated:YES];
    

    Hope that helps!


  • Translate

    Swift version of the accepted answer for easy copy pasting:

    let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
    scrollView.setContentOffset(bottomOffset, animated: true)
    

  • Translate

    Simplest Solution:

    [scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
    

  • Translate

    Just an enhancement to the existing answer.

    CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
    [self.scrollView setContentOffset:bottomOffset animated:YES];
    

    It takes care of the bottom inset as well (in case you're using that to adjust your scroll view when the keyboard is visible)


  • Translate

    Setting the content offset to the height of the content size is wrong: it scrolls the bottom of the content to the top of the scroll view, and thus out of sight.

    The correct solution is to scroll the bottom of the content to the bottom of the scroll view, like this (sv is the UIScrollView):

    CGSize csz = sv.contentSize;
    CGSize bsz = sv.bounds.size;
    if (sv.contentOffset.y + bsz.height > csz.height) {
        [sv setContentOffset:CGPointMake(sv.contentOffset.x, 
                                         csz.height - bsz.height) 
                    animated:YES];
    }
    

  • Translate

    A Swift 2.2 solution, taking contentInset into account

    let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
    scrollView.setContentOffset(bottomOffset, animated: true)
    

    This should be in an extension

    extension UIScrollView {
    
      func scrollToBottom() {
        let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height + contentInset.bottom)
        setContentOffset(bottomOffset, animated: true)
      }
    }
    

    Note that you may want to check if bottomOffset.y > 0 before scroll


  • Translate

    A swifty implementation:

    extension UIScrollView {
       func scrollToBottom(animated: Bool) {
         if self.contentSize.height < self.bounds.size.height { return }
         let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height)
         self.setContentOffset(bottomOffset, animated: animated)
      }
    }
    

    use it:

    yourScrollview.scrollToBottom(animated: true)
    

  • Translate

    Scroll To Top

    - CGPoint topOffset = CGPointMake(0, 0);
    - [scrollView setContentOffset:topOffset animated:YES];
    

    Scroll To Bottom

    - CGPoint bottomOffset = CGPointMake(0, scrollView.contentSize.height - self.scrollView.bounds.size.height);
     - [scrollView setContentOffset:bottomOffset animated:YES];
    

  • Translate

    What if contentSize is lower than bounds?

    For Swift it is:

    scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
    

  • Translate

    I also found another useful way of doing this in the case you are using a UITableview (which is a subclass of UIScrollView):

    [(UITableView *)self.view scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
    

  • Translate

    Using UIScrollView's setContentOffset:animated: function to scroll to the bottom in Swift.

    let bottomOffset : CGPoint = CGPointMake(0, scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
    scrollView.setContentOffset(bottomOffset, animated: true)
    

  • Translate

    With an (optional) footerView and contentInset, the solution is:

    CGPoint bottomOffset = CGPointMake(0, _tableView.contentSize.height - tableView.frame.size.height + _tableView.contentInset.bottom);
    if (bottomOffset.y > 0) [_tableView setContentOffset: bottomOffset animated: YES];
    

  • Translate

    valdyr, hope this will help you:

    CGPoint bottomOffset = CGPointMake(0, [textView contentSize].height - textView.frame.size.height);
    
    if (bottomOffset.y > 0)
     [textView setContentOffset: bottomOffset animated: YES];
    

  • Translate

    Category to the rescue!

    Add this to a shared utility header somewhere:

    @interface UIScrollView (ScrollToBottom)
    - (void)scrollToBottomAnimated:(BOOL)animated;
    @end
    

    And then to that utility implementation:

    @implementation UIScrollView(ScrollToBottom)
    - (void)scrollToBottomAnimated:(BOOL)animated
    {
         CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
         [self setContentOffset:bottomOffset animated:animated];
    }
    @end
    

    Then Implement it wherever you like, for instance:

    [[myWebView scrollView] scrollToBottomAnimated:YES];
    

  • Translate

    A good way to ensure the bottom of your content is visible is to use the formula:

    contentOffsetY = MIN(0, contentHeight - boundsHeight)
    

    This ensures the bottom edge of your content is always at or above the bottom edge of the view. The MIN(0, ...) is required because UITableView (and probably UIScrollView) ensures contentOffsetY >= 0 when the user tries to scroll by visibly snapping contentOffsetY = 0. This looks pretty weird to the user.

    The code to implement this is:

    UIScrollView scrollView = ...;
    CGSize contentSize = scrollView.contentSize;
    CGSize boundsSize = scrollView.bounds.size;
    if (contentSize.height > boundsSize.height)
    {
        CGPoint contentOffset = scrollView.contentOffset;
        contentOffset.y = contentSize.height - boundsSize.height;
        [scrollView setContentOffset:contentOffset animated:YES];
    }
    

  • Translate

    If you don't need animation, this works:

    [self.scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:NO];
    

  • Translate

    While Matt solution seems correct to me you need to take in account also the collection view inset if there is one that has been set-up.

    The adapted code will be:

    CGSize csz = sv.contentSize;
    CGSize bsz = sv.bounds.size;
    NSInteger bottomInset = sv.contentInset.bottom;
    if (sv.contentOffset.y + bsz.height + bottomInset > csz.height) {
        [sv setContentOffset:CGPointMake(sv.contentOffset.x, 
                                         csz.height - bsz.height + bottomInset) 
                    animated:YES];
    }
    

  • Translate

    In swift:

       if self.mainScroll.contentSize.height > self.mainScroll.bounds.size.height {
            let bottomOffset = CGPointMake(0, self.mainScroll.contentSize.height - self.mainScroll.bounds.size.height);
            self.mainScroll.setContentOffset(bottomOffset, animated: true)
        }
    

  • Translate

    Solution to scroll to last item of a table View :

    Swift 3 :

    if self.items.count > 0 {
            self.tableView.scrollToRow(at:  IndexPath.init(row: self.items.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: true)
    }
    

  • Translate

    It looks like all of the answers here didn't take the safe area into consideration. Since iOS 11, iPhone X had a safe area introduced. This may affect the scrollView's contentInset.

    For iOS 11 and above, to properly scroll to the bottom with the content inset included. You should use adjustedContentInset instead of contentInset. Check this code:

    • Swift:
    let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.height + scrollView.adjustedContentInset.bottom)
    scrollView.setContentOffset(bottomOffset, animated: true)
    
    • Objective-C
    CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.adjustedContentInset.bottom);
    [self.scrollView setContentOffset:bottomOffset animated:YES];
    
    • Swift extension (this keeps the original contentOffset.x):
    extension UIScrollView {
        func scrollsToBottom(animated: Bool) {
            let bottomOffset = CGPoint(x: contentOffset.x,
                                       y: contentSize.height - bounds.height + adjustedContentInset.bottom)
            setContentOffset(bottomOffset, animated: animated)
        }
    }
    

    References:


  • Translate

    Didn't work for me, when I tried to use it in UITableViewController on self.tableView (iOS 4.1), after adding footerView. It scrolls out of the borders, showing black screen.

    Alternative solution:

     CGFloat height = self.tableView.contentSize.height; 
    
     [self.tableView setTableFooterView: myFooterView];
     [self.tableView reloadData];
    
     CGFloat delta = self.tableView.contentSize.height - height;
     CGPoint offset = [self.tableView contentOffset];
     offset.y += delta;
    
     [self.tableView setContentOffset: offset animated: YES];
    

  • Translate
    CGFloat yOffset = scrollView.contentOffset.y;
    
    CGFloat height = scrollView.frame.size.height;
    
    CGFloat contentHeight = scrollView.contentSize.height;
    
    CGFloat distance = (contentHeight  - height) - yOffset;
    
    if(distance < 0)
    {
        return ;
    }
    
    CGPoint offset = scrollView.contentOffset;
    
    offset.y += distance;
    
    [scrollView setContentOffset:offset animated:YES];
    

  • Translate

    I found that contentSize doesn't really reflect the actual size of the text, so when trying to scroll to the bottom, it will be a little bit off. The best way to determine the actual content size is actually to use the NSLayoutManager's usedRectForTextContainer: method:

    UITextView *textView;
    CGSize textSize = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size;
    

    To determine how much text actually is shown in the UITextView, you can calculate it by subtracting the text container insets from the frame height.

    UITextView *textView;
    UIEdgeInsets textInsets = textView.textContainerInset;
    CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;
    

    Then it becomes easy to scroll:

    // if you want scroll animation, use contentOffset
    UITextView *textView;
    textView.contentOffset = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);
    
    // if you don't want scroll animation
    CGRect scrollBounds = textView.bounds;
    scrollBounds.origin = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);
    textView.bounds = scrollBounds;
    

    Some numbers for reference on what the different sizes represent for an empty UITextView.

    textView.frame.size = (width=246, height=50)
    textSize = (width=10, height=16.701999999999998)
    textView.contentSize = (width=246, height=33)
    textView.textContainerInset = (top=8, left=0, bottom=8, right=0)
    

  • Translate

    For Horizontal ScrollView

    If you like me has a Horizontal ScrollView and want to scroll to end of it (in my case to most right of it), you need to change some parts of the accepted answer:

    Objective-C

    CGPoint rightOffset = CGPointMake(self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, 0 );
    [self.scrollView setContentOffset:rightOffset animated:YES];
    

    Swift

    let rightOffset: CGPoint = CGPoint(x: self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, y: 0)
    self.scrollView.setContentOffset(rightOffset, animated: true)
    

  • Translate

    Xamarin.iOS version of accepted answer

    var bottomOffset = new CGPoint (0,
         scrollView.ContentSize.Height - scrollView.Frame.Size.Height
         + scrollView.ContentInset.Bottom);
    
    scrollView.SetContentOffset (bottomOffset, false);
    

  • Translate

    Xamarin.iOS version for UICollectionView of the accepted answer for ease in copying and pasting

    var bottomOffset = new CGPoint (0, CollectionView.ContentSize.Height - CollectionView.Frame.Size.Height + CollectionView.ContentInset.Bottom);          
    CollectionView.SetContentOffset (bottomOffset, false);