The proposed answers have a significant downside: If the user tries to scroll during an ongoing animation, the input will be cause jittering as the animation will forcefully keep on going until completion. If you set a really long animation duration, the issue becomes apparent. Here is my use case, animating a scroll view to snap to a section title (while trying to scroll up at the same time):
I propose the following subclass:
public class AnimatingScrollView: NSScrollView {
// This will override and cancel any running scroll animations
override public func scroll(_ clipView: NSClipView, to point: NSPoint) {
CATransaction.begin()
CATransaction.setDisableActions(true)
contentView.setBoundsOrigin(point)
CATransaction.commit()
super.scroll(clipView, to: point)
}
public func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(toPoint)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
By overriding the normal scroll(_ clipView: NSClipView, to point: NSPoint)
(invoked when the user scrolls) and manually performing the a scroll inside a CATransaction
with setDisableActions
, we cancel the current animation. However, we don't call reflectScrolledClipView
, instead we call super.scroll(clipView, to: point)
, which will perform other necessary internal procedures and then perform reflectScrolledClipView
.
Above class produces better results: