Reputation: 13267
How do I make a UIScrollView scroll to the top?
Upvotes: 190
Views: 143574
Reputation: 12385
iOS 16
For table and collection views, the following always works for me:
let top = CGRect(x: 0, y: 0, width: 1, height: 1)
tableView.scrollRectToVisible(top, animated: true)
collectionView.scrollRectToVisible(top, animated: true)
For scroll views:
let top = CGPoint(x: 0, y: -adjustedContentInset.top)
scrollView.setContentOffset(top, animated: animated)
adjustedContentInset
returns the insets applied by the safe area (if any) and any custom insets applied after instantiation. If either safe or custom insets are applied, the content inset of the scroll view when it's at its top will be negative, not zero, which is why this property should be used.
Upvotes: 2
Reputation: 34983
In modern iOS, set the the scroll view's content offset back to its top left adjustedContentInset
:
let point = CGPoint(x: -scrollView.adjustedContentInset.left,
y: -scrollView.adjustedContentInset.top)
scrollView.setContentOffset(point, animated: true)
Upvotes: 2
Reputation: 17
iOS 2.0+ Mac Catalyst 13.0+
You can try: scrollView.scrollsToTop = true
You can refer it from documentation of developer.apple.com
Upvotes: -3
Reputation: 20710
Try to play around with the new adjustedContentInset
(It should even work with prefersLargeTitles
, safe area
etc.)
For example (scroll to the top):
var offset = CGPoint(
x: -scrollView.contentInset.left,
y: -scrollView.contentInset.top)
if #available(iOS 11.0, *) {
offset = CGPoint(
x: -scrollView.adjustedContentInset.left,
y: -scrollView.adjustedContentInset.top)
}
scrollView.setContentOffset(offset, animated: true)
Upvotes: 54
Reputation: 4935
In SWIFT 5 Just set content Offset to zero
scrollView.setContentOffset(CGPoint.zero, animated: true)
Upvotes: 8
Reputation: 1658
For Swift 4
scrollView.setContentOffset(.zero, animated: true)
Upvotes: 66
Reputation: 2308
It's very common when your navigation bar overlaps the small portion of the scrollView content and it looks like content starts not from the top. For fixing it I did 2 things:
Upvotes: 3
Reputation: 617
I think I have an answer that should be fully compatible with iOS 11 as well as prior versions (for vertical scrolling)
This takes into account the new adjustedContentInset and also accounts for the additional offset required when prefersLargeTitles is enabled on the navigationBar which appears to require an extra 52px offset on top of whatever the default is
This was a little tricky because the adjustedContentInset changes depending on the titleBar state (large title vs small title) so I needed to check and see what the titleBar height was and not apply the 52px offset if its already in the large state. Couldn't find any other method to check the state of the navigationBar so if anyone has a better option than seeing if the height is > 44.0 I'd like to hear it
func scrollToTop(_ scrollView: UIScrollView, animated: Bool = true) {
if #available(iOS 11.0, *) {
let expandedBar = (navigationController?.navigationBar.frame.height ?? 64.0 > 44.0)
let largeTitles = (navigationController?.navigationBar.prefersLargeTitles) ?? false
let offset: CGFloat = (largeTitles && !expandedBar) ? 52: 0
scrollView.setContentOffset(CGPoint(x: 0, y: -(scrollView.adjustedContentInset.top + offset)), animated: animated)
} else {
scrollView.setContentOffset(CGPoint(x: 0, y: -scrollView.contentInset.top), animated: animated)
}
}
Inspired by Jakub's solution
Upvotes: 3
Reputation: 14890
Here is a Swift extension that makes it easy:
extension UIScrollView {
func scrollToTop() {
let desiredOffset = CGPoint(x: 0, y: -contentInset.top)
setContentOffset(desiredOffset, animated: true)
}
}
Usage:
myScrollView.scrollToTop()
Upvotes: 76
Reputation: 355
Answer for Swift 2.0/3.0/4.0 and iOS 7+:
let desiredOffset = CGPoint(x: 0, y: -self.scrollView.contentInset.top)
self.scrollView.setContentOffset(desiredOffset, animated: true)
Upvotes: 19
Reputation: 125
In iOS7 I had trouble getting a particular scrollview to go to the top, which worked in iOS6, and used this to set the scrollview to go to the top.
[self.myScroller scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
Upvotes: 8
Reputation: 1323
Scroll to top for UITableViewController
, UICollectionViewController
or any UIViewController
having UIScrollView
extension UIViewController {
func scrollToTop(animated: Bool) {
if let tv = self as? UITableViewController {
tv.tableView.setContentOffset(CGPoint.zero, animated: animated)
} else if let cv = self as? UICollectionViewController{
cv.collectionView?.setContentOffset(CGPoint.zero, animated: animated)
} else {
for v in view.subviews {
if let sv = v as? UIScrollView {
sv.setContentOffset(CGPoint.zero, animated: animated)
}
}
}
}
}
Upvotes: 0
Reputation: 323
Swift 3.0.1 version of rob mayoff's answer :
self.scrollView.setContentOffset(
CGPoint(x: 0,y: -self.scrollView.contentInset.top),
animated: true)
Upvotes: 4
Reputation: 1263
I tried all the ways. But nothing worked for me. Finally I did like this.
I added self.view .addSubview(self.scroll)
line of code in the viewDidLoad. After started setting up frame for scroll view and added components to scroll view.
It worked for me.
Make sure you added self.view .addSubview(self.scroll)
line in the beginning. then you can add UI elements.
Upvotes: -3
Reputation: 385500
[self.scrollView setContentOffset:
CGPointMake(0, -self.scrollView.contentInset.top) animated:YES];
[self.scrollView setContentOffset:CGPointZero animated:YES];
or if you want to preserve the horizontal scroll position and just reset the vertical position:
[self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, 0)
animated:YES];
Upvotes: 331
Reputation: 54121
To fully replicate the status bar scrollToTop behavior we not only have to set the contentOffset but also want to make sure the scrollIndicators are displayed. Otherwise the user can quickly get lost.
The only public method to accomplish this is flashScrollIndicators
. Unfortunately, calling it once after setting the contentOffset has no effect because it's reset immediately. I found it works when doing the flash each time in scrollViewDidScroll:
.
// define arbitrary tag number in a global constants or in the .pch file
#define SCROLLVIEW_IS_SCROLLING_TO_TOP_TAG 19291
- (void)scrollContentToTop {
[self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.scrollView.contentInset.top) animated:YES];
self.scrollView.tag = SCROLLVIEW_IS_SCROLLING_TO_TOP_TAG;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.scrollView.tag = 0;
});
}
In your UIScrollViewDelegate (or UITable/UICollectionViewDelegate) implement this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.tag == SCROLLVIEW_IS_SCROLLING_TO_TOP_TAG) {
[scrollView flashScrollIndicators];
}
}
The hide delay is a bit shorter compared to the status bar scrollToTop behavior but it still looks nice.
Note that I'm abusing the view tag to communicate the "isScrollingToTop" state because I need this across view controllers. If you're using tags for something else you might want to replace this with an iVar or a property.
Upvotes: 2
Reputation: 46578
Use setContentOffset:animated:
[scrollView setContentOffset:CGPointZero animated:YES];
Upvotes: 23