NSScrollView: Overriding "scrollWheel(with:)" can cause a never-ending live scroll

Overview

I'm developing an app that depends on NSScrollView to implement a scrollable and zoomable canvas. I need to override NSScrollView.scrollWheel(with:) to implement Option-zooming (holding Option while scrolling up/down to zoom in/out).

I've found that simply adding these three lines of code in my NSScrollView subclass:

override func scrollWheel(with event: NSEvent) {
    super.scrollWheel(with: event)
}

is enough to cause a live scroll to never end, and NSScrollView.didEndLiveScrollNotification to never fire. My app depends on that notification firing so that it can disable some interactions during a live scroll.

Furthermore, while the NSScrollView is "stuck" in the live scroll, it's not possible to perform a live magnify. There are also some other effects, including elevated CPU usage and persistent scroll indicators.

The issue is occurring with Xcode 27.1 (27A9275) and macOS 27.2 (26B123).

I have modified the macOS App project template to showcase the issue. The sample project, along with a screen recording of the issue, can be downloaded from here:

https://drive.google.com/drive/folders/1F5sF2NA230NkGbQdLfbV6vM1XmfY1-uT?usp=sharing

I hope that Apple can fix this issue soon, as it's a frustrating user experience when the canvas gets stuck while scrolling.

I've already filed this in Feedback Assistant under FB25362729.

Steps to Reproduce

To recreate the issue, follow these steps:

  1. Download and extract the "Xcode Project.zip" file from the link above.
  2. Open the extracted "NSScrollViewTest" project in Xcode.
  3. Build and run the "NSScrollViewTest" app.
  4. Scroll all the way to the right edge of the gray canvas using the horizontal scroll gesture on Magic Trackpad.

If you comment out lines 8–10 of ScrollView.swift, the issue goes away.

The "Issue Recording.mov" file from the link above provides a detailed video of how to recreate the issue.

That's a gesture, not a scroll. This method was designed for a mouse wheel and may or may not be simulated properly on modern systems. There are many historical layers of hacks on top of each other to support mice, mouse wheels, trackpads, elasticity, etc.

Obviously your overridden method does nothing, so you shouldn't override it.

The question you want to answer is what are you trying to accomplish by overriding scrollWheel(with:)? There's probably a better way to do it. The only time I ever override scrollWheel(with:) is to prevent scrolling, or otherwise hack some other system bug that would be worse.