NSScrollView scrolling hitch

When scrolling a basic NSScrollView there seems to be a sudden jump after each flick. Scrolling does not appear smooth and is disorientating.

A scroll jump seems to happen directly after letting go of a scroll flick using a trackpad/mouse. Right at that moment the scroll turns into a momentum scroll, slowly decreasing the speed. But the first frame after the gesture the content jumps forward, more than what is expected.

Observations:

  1. Counterintuitively, scrolling appears to be smoother when disabling NSScrollView.isCompatibleWithResponsiveScrolling. If disabled using a custom NSScrollView subclass there is no large jump anymore.
  2. Scrolling also appears to be smoother using a SwiftUI ScrollView. I assume that has the same behaviour as a disabled isCompatibleWithResponsiveScrolling
  3. Ironically a WKWebView scrolls much smoother. No sudden jump is observable. It also seems to scroll with faster acceleration, but the individual frames do appear smoother. Why is this better than a native NSScrollView?
  4. Elastic scrolling at the bounds of the scroll view also appears much smoother for WKWebViews. When pulling to refresh there is a jump for NSScrollView/SwiftUI, but not for WKWebView.
  5. When using an NSScrollView with isCompatibleWithResponsiveScrolling disabled, scrolling appears just as smooth as WKWebView on macOS 13 Ventura and below. On macOS 14 Sonoma scrolling behaviour is suddenly different.

Please see a sample project with 4 different scroll views side by side: https://github.com/floorish/ScrollTest Screen recordings show the sudden jumps when scrolling and when elastic scrolling. Tested on Intel & Arm Macs, macOS 11 Big Sur through 15 Sequoia, built with Xcode 16.

Should isCompatibleWithResponsiveScrolling be disabled on Sonoma+? Are there any drawbacks? There is also no overdraw anymore since Monterey, as described in https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/#10_9Scrolling

Even with responsive scrolling disabled, why is WKWebView scrolling much smoother than NSScrollView?

I tested your code on Mac Sonoma and Xcode 16.2.

I did not notice the difference between the various cases unless I really focused to try to notice. And it is nearly imperceptible.

It seems to occur only when a new row appears at the end of scrolling.

I’m so glad to see someone else mention this! I honestly thought I might be going crazy, thinking that inertia doesn’t feel quite right in NSScrollView.

When scrolling a basic NSScrollView there seems to be a sudden jump after each flick. Scrolling does not appear smooth and is disorientating.

I too have noticed this for years, and it’s always bothered me!

A few months ago I tried to investigate these inconsistencies. It turns out that WebKit contains a custom reimplementation of NSScrollView which tries (and IMO succeeds) at being essentially indistinguishable from the real thing, except for the improved smoothness and faster scrolling speed, as you mentioned. I believe at least some of the hitches in AppKit you’re seeing aren’t down to rendering or view updates or anything like that, but simply because AppKit scroll event inertia is “stepped”: https://pavelfatin.com/images/scrolling-with-pleasure/scrolling-velocity.png. I think WebKit might also have a custom reimplementation of inertia (ignoring AppKit’s scroll inertia events, instead synthesizing its own), which would explain the difference. It, like every custom scroll view, also reimplements elasticity.

As for responsive scrolling, I personally can’t tell the difference between NSScrollView with responsive scrolling enabled vs disabled.

Thanks for confirming you're seeing the same issue.

The difference between responsive scrolling enabled vs disabled is indeed small.

You can override scrollWheel(with:) in a custom NSScrollview and output event.scrollingDeltaY and event.momentumPhase. You'll notice a jump when the Phase changes from .none (gesture) to .began (stopped gesture).

The final .none event has a zero value for scrollingDeltaY. But the view scrolls way more than expected.

...
Event  0.016  -17.0 .none
Bounds 0.016  -17.0
Event  0.016  -20.0 .none
Bounds 0.016  -20.0
Event  0.016  -26.0 .none
Event  0.000    0.0 .none
Bounds 0.016  -59.0  <- jump
Event  0.018  -29.0 .began
Bounds 0.016  -19.5
Event  0.014  -31.0 .changed
Bounds 0.016  -18.5
...

The problem is that by overriding scrollWheel(with:) responsive scrolling is implicitly disabled. So these values may not be the same without override (the jump is larger when checking screen recording + responsive scroll). But it is clear that the inertia logic is not smooth when switching from gesture to momentum.

Not sure if that's due to "stepped" scrolling (interesting link!) but the custom WebKit implementation is indeed much smoother.

NSScrollView scrolling hitch
 
 
Q