UIScrollView adjusts contentOffset when contentSize changes

Viewed 47941

I am adjusting a detail view controller's state, just before it is pushed on a navigationController:

[self.detailViewController detailsForObject:someObject];
[self.navigationController pushViewController:self.detailViewController
                                     animated:YES];

In the DetailViewController a scrollView resides. Which content I resize based on the passed object:

- (void)detailsForObject:(id)someObject {
    // set some textView's content here
    self.contentView.frame = <rect with new calculated size>;

    self.scrollView.contentSize = self.contentView.frame.size;
    self.scrollView.contentOffset = CGPointZero;
}

Now, this all works, but the scrollView adjusts it's contentOffset during the navigationController's slide-in animation. The contentOffset will be set to the difference between the last contentSize and the new calculated one. This means that the second time you open the detailsView, the details will scroll to some unwanted location. Even though I'm setting the contentOffset to CGPointZero explicitly.

I found that resetting the contentOffset in - viewWillAppear has no effect. The best I could come up with is resetting the contentOffset in viewDidAppear, causing a noticeable up and down movement of the content:

- (void)viewDidAppear:(BOOL)animated {
    self.scrollView.contentOffset = CGPointZero;
}

Is there a way to prevent a UIScrollView from adjusting its contentOffset when its contentSize is changed?

8 Answers

Occurs when pushing a UIViewController containing a UIScrollView using a UINavigationController.

iOS 11+

Solution 1 (Swift Code):

scrollView.contentInsetAdjustmentBehavior = .never

Solution 2 (Storyboard)

enter image description here

iOS 7

Solution 1 (Code)

Set @property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets to NO.

Solution 2 (Storyboard)

Uncheck the Adjust Scroll View Insets

Adjust Scroll View Insets

iOS 6

Solution (Code)

Set the UIScrollView's property contentOffset and contentInset in viewWillLayoutSubviews. Sample code:

- (void)viewWillLayoutSubviews{
  [super viewWillLayoutSubviews];
  self.scrollView.contentOffset = CGPointZero;
  self.scrollView.contentInset = UIEdgeInsetsZero;
}

Here's what worked for me:
In the storyboard, in the Size Inspector for the scrollView, set Content Insets Adjustment Behavior to "Never".

enter image description here

I was experiencing a similar problem, where UIKit was setting the contentOffset of my scrollView during push animations.

None of these solutions were working for me, maybe because I was supporting iOS 10 and iOS 11.

I was able to fix my issue by subclassing my scrollview to keep UIKit from changing my offsets after the scrollview had been removed from the window:

/// A Scrollview that only allows the contentOffset to change while it is in the window hierarchy. This can keep UIKit from resetting the `contentOffset` during transitions, etc.
class LockingScrollView: UIScrollView {
    override var contentOffset: CGPoint {
        get {
            return super.contentOffset
        }
        set {
            if window != nil {
                super.contentOffset = newValue
            }
        }
    }
}

Adding to KarenAnne's answer:

iOS 11+

automaticallyAdjustsScrollViewInsets was deprecated

Use this istead:

Storyboards:

enter image description here

Code (Swift):

scrollView.contentInsetAdjustmentBehavior = .never
Related