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?
30 Answers
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)
9 Comments
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);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)let bottomOffsetY = max(collectionView.contentSize.height - collectionView.bounds.height + collectionView.contentInset.bottom, 0)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)
5 Comments
Simplest Solution:
[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
2 Comments
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)
Comments
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)
1 Comment
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];
}
4 Comments
if (sv.contentOffset.y + csz.height > bsz.height) {?setContentOffset: command that's important.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
Comments
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];
Comments
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:
Comments
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)
}
Comments
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];
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];
1 Comment
tableView.frame may be invalidAs explained here https://janeshswift.com/ios/swift/how-to-scroll-to-a-position-programmatically-in-uiscrollview/
We can create a custom UIScrollView extension as
extension UIScrollView {
func scrollToTop(animated: Bool = false) {
setContentOffset(CGPoint(x: contentOffset.x, y: -adjustedContentInset.top), animated: animated)
}
var bottomContentOffsetY: CGFloat {
max(contentSize.height - bounds.height + adjustedContentInset.bottom, -adjustedContentInset.top)
}
func scrollToBottom(animated: Bool = false) {
setContentOffset(CGPoint(x: contentOffset.x, y: bottomContentOffsetY), animated: animated)
}
func scrollToLeading(animated: Bool = false) {
setContentOffset(CGPoint(x: -adjustedContentInset.left, y: contentOffset.y), animated: animated)
}
var trailingContentOffsetX: CGFloat {
max(contentSize.width - bounds.width + adjustedContentInset.right, -adjustedContentInset.left)
}
func scrollToTrailing(animated: Bool = false) {
setContentOffset(CGPoint(x: trailingContentOffsetX, y: contentOffset.y), animated: animated)
}
func scrollViewToVisible(_ view: UIView, animated: Bool = false) {
scrollRectToVisible(convert(view.bounds, from: view), animated: true)
}
var isOnTop: Bool {
contentOffset.y <= -adjustedContentInset.top
}
var isOnBottom: Bool {
contentOffset.y >= bottomContentOffsetY
}
}
Use It as --
DispatchQueue.main.async {
self.itemsScrollView.scrollToBottom()
}
1 Comment
isOnBottom will return false while scrolling but will then bounce to the bottom because the content size is smaller than the scrollview. scrollView.contentSize.height < (scrollView.bounds.height - scrollView.adjustedContentInset.bottom - scrollView.adjustedContentInset.top) should resolve this issue (if you need it to act this way).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)
Comments
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];
1 Comment
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];
1 Comment
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];
}
Comments
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];
Comments
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];
}
Comments
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];
Comments
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)
Comments
To scroll to the bottom end, we have to work with the target view maximum height.
import UIKit
extension UIScrollView {
func scrollToBottomOf(targetView: UIView, animated: Bool) {
setContentOffset(CGPoint(x:targetView.frame.minX, y:targetView.frame.maxY), animated: animated)
}
}
//func invocation example
optionScrollView.scrollToBottomOf(targetView: self.optionsStackView, animated: false)
Comments
This is code is the modified version of the accepted answer. The problem with the accepted answer is that it does not check if it needs to be scrolled or not. In this piece of code i check if it needs to scroll then perform the action.
let contentHeight = scrollView.contentSize.height
let scrollViewHeight = scrollView.bounds.height
let contentInsetBottom = scrollView.contentInset.bottom
let bottomOffset = CGPoint(x: 0, y: contentHeight - scrollViewHeight +
contentInsetBottom)
if scrollView.contentOffset.y < bottomOffset.y {
scrollView.setContentOffset(bottomOffset, animated: animated)
}
Comments
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);