314

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?

Ahmad F
  • 26,570
  • 13
  • 76
  • 124
nico
  • 9,358
  • 8
  • 23
  • 28

29 Answers29

650

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:

Objective-C:

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

Swift:

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

Hope that helps!

Guillaume Algis
  • 10,000
  • 5
  • 39
  • 68
Ben Gotow
  • 14,276
  • 3
  • 39
  • 47
  • 25
    you probably want to the following to scroll to the bottom, instead of out of the scrollview: CGPoint bottomOffset = CGPointMake(0, [self.scrollView contentSize].height - self.scrollView.frame.size.height); – Grantland Chew Nov 02 '11 at 00:22
  • it was fun that my property was also self.scrollView, so this worked from copy paste – wfbarksdale Mar 21 '14 at 23:25
  • 71
    Your are not taking insets into account. I think You should prefer this bottomOffset: `CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);` – Martin Dec 16 '14 at 10:24
  • 1
    this not work in Landscape mode! not completely scroll to bottom – Mo Farhand Aug 10 '15 at 12:58
  • `CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);` does not really work either when insets are set – Daniel Sep 24 '15 at 12:27
  • 3
    It wil not work properly if `contentSize` is lower than `bounds`. So it should be like this: `scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)` – Bartłomiej Semańczyk Jan 16 '16 at 19:55
  • 1
    This handles bottom inset, and going beyond 0: `let bottomOffsetY = max(collectionView.contentSize.height - collectionView.bounds.height + collectionView.contentInset.bottom, 0)` – Senseful Aug 24 '16 at 19:01
  • If you're using a collection view don't forget the bot safe area inset. `let bot = max(cv.contentSize.height - cv.bounds.height + cv.contentInset.bottom + cv.safeAreaInsets.bottom, 0)` – Xavier L. Jun 14 '18 at 17:30
  • Why does this not work in viewWillAppear? – Unikorn Jan 25 '21 at 18:22
  • It doesn't work for me – Cublax Mar 07 '21 at 12:54
153

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)
Guillaume Algis
  • 10,000
  • 5
  • 39
  • 68
Esqarrouth
  • 35,175
  • 17
  • 147
  • 154
53

Simplest Solution:

[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
Hai Hw
  • 1,297
  • 1
  • 16
  • 23
29

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)
duan
  • 6,962
  • 3
  • 40
  • 62
28

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)

vivek241
  • 646
  • 1
  • 7
  • 18
  • This should be the correct answer... not the other ones that'll break if they ever have a keyboard pop up. – Ben Guild Jun 21 '16 at 07:38
19

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];
}
matt
  • 447,615
  • 74
  • 748
  • 977
  • 1
    Shouldn't it be `if (sv.contentOffset.y + csz.height > bsz.height) {`? – i_am_jorf Nov 14 '11 at 18:30
  • @jeffamaphone - Thanks for asking. Actually at this point I'm not even sure what purpose the condition was supposed to serve! It's the `setContentOffset:` command that's important. – matt Nov 14 '11 at 20:46
  • I presume its to avoid the setContentOffset call if it isn't going to do anything? – i_am_jorf Nov 15 '11 at 16:30
  • @jeffamaphone - It used to be that after device rotation a scroll view could end with its content bottom higher than the bottom of the frame (because if we rotate a scroll view its content offset remains constant, but the scroll view height may have grown due to autoresizing). The condition says: If that happens, put the content bottom back down at the bottom of the frame. But it was silly of me to include the condition when I pasted in the code. The condition *is* correctly testing for what it was testing for, though. – matt Nov 15 '11 at 17:21
16

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

onmyway133
  • 38,911
  • 23
  • 231
  • 237
15

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)
Bartłomiej Semańczyk
  • 52,820
  • 43
  • 206
  • 318
13

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];
Rakesh
  • 3,299
  • 2
  • 19
  • 41
Tahir Waseem
  • 141
  • 2
  • 6
7

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)
Kim
  • 101
  • 1
  • 2
7

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:

Honghao Zhang
  • 1,083
  • 17
  • 24
7

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];
Jason Plank
  • 2,322
  • 4
  • 29
  • 39
nico
  • 9,358
  • 8
  • 23
  • 28
6

Swift:

You could use an extension like this:

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

Use:

scrollView.scrollsToBottom(animated: true)
Mauro García
  • 107
  • 1
  • 2
6

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];
Mahmut Acar
  • 643
  • 2
  • 7
  • 21
Pieter
  • 511
  • 6
  • 6
4

If you somehow change scrollView contentSize (ex. add something to stackView which is inside scrollView) you must call scrollView.layoutIfNeeded() before scrolling, otherwise it does nothing.

Example:

scrollView.layoutIfNeeded()
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
if(bottomOffset.y > 0) {
    scrollView.setContentOffset(bottomOffset, animated: true)
}
Robert Koval
  • 331
  • 1
  • 2
3

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];
Jason Plank
  • 2,322
  • 4
  • 29
  • 39
Misha
  • 31
  • 1
3

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)
Esmaeil
  • 181
  • 2
  • 7
2

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];
BadPirate
  • 24,683
  • 10
  • 85
  • 118
1

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];
}
jjrscott
  • 1,194
  • 10
  • 12
1

If you don't need animation, this works:

[self.scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:NO];
Krys Jurgowski
  • 2,451
  • 15
  • 24
1

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];
}
tiguero
  • 11,171
  • 5
  • 41
  • 61
1

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)
    }
Ponja
  • 664
  • 1
  • 6
  • 15
1

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)
}
MarionFlex
  • 92
  • 4
0
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];
8suhas
  • 1,430
  • 10
  • 20
0

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)
mikeho
  • 6,080
  • 2
  • 31
  • 46
0

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];
rptwsthi
  • 9,855
  • 10
  • 65
  • 102
valdyr
  • 188
  • 1
  • 10
0

Extend UIScrollView to add a scrollToBottom method:

extension UIScrollView {
    func scrollToBottom(animated:Bool) {
        let offset = self.contentSize.height - self.visibleSize.height
        if offset > self.contentOffset.y {
            self.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
        }
    }
}
-1

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);
TheTiger
  • 12,822
  • 3
  • 53
  • 77
Abdur
  • 1
  • 1
-2

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);
JimHawkins
  • 4,107
  • 8
  • 28
  • 53
Abdur
  • 1
  • 1