41
votes

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
Why are you adjusting the content offset within an animation block? Skip the CATransaction stuff, so it doesn't animate. Then it won't look choppy.Andrew
Removing the CATransaction stuff indeed does not change a thing, so it is removed.Berik
Are you sure viewDidAppear is getting called? I'd set a breakpoint or a NSLog statement in it. Also, have you tried switching the order of your calls, i.e. setting the detailsForObject after pushing?Andrew
Yes it is called, and it set's the correct contentOffset to the scrollView. But the contentOffset animation occurs before viewDidAppear is called.Berik

8 Answers

60
votes

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;
}
15
votes

The cause of this problem remains unclear, though I've found a solution. By resetting the content size and offset before adjusting them, the UIScrollView won't animate:

- (void)detailsForObject:(id)someObject {
    // These 2 lines solve the issue:
    self.scrollView.contentSize = CGSizeZero;
    self.scrollView.contentOffset = CGPointZero;

    // 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;
}
3
votes

I had the same issue with a UIScrollview, where the problem was caused by not setting the contentSize. After setting the contentSize to the number of items this problem was solved.

self.headerScrollView.mainScrollview.contentSize = CGSizeMake(320 * self.sortedMaterial.count, 0);
3
votes

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

0
votes

Is your scrollView the root view of the DetailViewController? If yes, try wrapping the scrollView in a plain UIView and make the latter the root view of DetailViewController. Since UIViews don't have a contentOffset property, they are immune to content offset adjustments made by the navigation controller (due to the navigation bar, etc.).

0
votes

I experienced the problem, and for a specific case - I don't adjust the size - I used the following:

float position = 100.0;//for example

SmallScroll.center = CGPointMake(position + SmallScroll.frame.size.width / 2.0, SmallScroll.center.y);

Same would work with y: anotherPosition + SmallScroll.frame.size.height / 2.0

So if you don't need to resize, this is a quick and painless solution.

0
votes

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
            }
        }
    }
}
0
votes

Adding to KarenAnne's answer:

iOS 11+

automaticallyAdjustsScrollViewInsets was deprecated

Use this istead:

Storyboards:

enter image description here

Code (Swift):

scrollView.contentInsetAdjustmentBehavior = .never