How to properly animate UIScrollView contentOffset

A nice way to do this is with the AnimationEngine library. It's a very small library: six files, with three more if you want damped spring behavior.

Behind the scenes it uses a CADisplayLink to run your animation block once every frame. You get a clean block-based syntax that's easy to use, and a bunch of interpolation and easing functions that save you time.

To animate contentOffset:

startOffset = scrollView.contentOffset;
endOffset = ..

// Constant speed looks good...
const CGFloat kTimelineAnimationSpeed = 300;
CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;

[INTUAnimationEngine animateWithDuration:timelineAnimationDuration
                                   delay:0
                                  easing:INTULinear
                              animations:^(CGFloat progress) {
                                    self.videoTimelineView.contentOffset = 
                                         INTUInterpolateCGPoint(startOffset, endOffset, progress);
                              }
                             completion:^(BOOL finished) {
                                   autoscrollEnabled = YES;
                             }];

Try this:

UIView.animate(withDuration: 0.6, animations: {
        self.view.collectionView.contentOffset = newOffset
        self.view.layoutIfNeeded()
    }, completion: nil)

Did you try the same approach, but with disabled animation in scrollViewDidScroll ?

On iOS 7, you could try wrapping your code in scrollViewDidScroll in

[UIView performWithoutAnimation:^{
       //Your code here
    }];

on previous iOS versions, you could try:

  [CATransaction begin];
    [CATransaction setDisableActions:YES];
     //Your code here
    [CATransaction commit];

Update:

Unfortunately that's where you hit the tough part of the whole thing. setContentOffset: calls the delegate just once, it's equivalent to setContentOffset:animated:NO, which again calls it just once.

setContentOffset:animated:YES calls the delegate as the animation changes the bounds of the scrollview and you want that, but you don't want the provided animation, so the only way around this that I can come up with is to gradually change the contentOffset of the scrollview, so that the animation system doesn't just jump to the final value, as is the case at the moment.

To do that you can look at keyframe animations, like so for iOS 7:

[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
    [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
       [self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
    }];
    [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
        [self setContentOffset:CGPointMake(index*elementWidth, 0)];
    }];
} completion:^(BOOL finished) {
    //Completion Block
}];

This will get you two updates and of course you could use some math and a loop to add up a lot more of these with the appropriate timings.

On previous iOS versions, you'll have to drop to CoreAnimation for keyframe animations, but it's basically the same thing with a bit different syntax.

Method 2: You can try polling the presentationLayer of the scrollview for any changes with a timer that you start at the beginning of the animation, since unfortunately the presentationLayer's properties aren't KVO observable. Or you can use needsDisplayForKey in a subclass of the layer to get notified when the bounds change, but that'll require some work to set up and it does cause redrawing, which might affect performance.

Method 3: Would be to dissect exactly what happens to the scrollView when animated is YES try and intercept the animation that gets set on the scrollview and change its parameters, but since this would be the most hacky, breakable due to Apple's changes and trickiest method, I won't go into it.