SwiftUI 2.0 ScrollView如何实现响应式滚动?
Great question! I ran into this exact issue with SwiftUI's ScrollView on macOS Big Sur too—out of the box, it uses non-responsive scrolling (matching the behavior when isCompatibleWithResponsiveScrolling is set to false in AppKit), while List gets the smooth, responsive scrolling we expect. Here are two solid solutions to fix this:
1. Custom NSViewRepresentable Wrapper (No Third-Party Dependencies)
The most stable approach is to wrap a native NSScrollView yourself, where you explicitly enable responsive scrolling. This gives you full control over the scroll view's behavior:
import SwiftUI struct ResponsiveScrollView<Content: View>: NSViewRepresentable { private let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } func makeNSView(context: Context) -> NSScrollView { let scrollView = NSScrollView() // Enable standard scrollers and responsive scrolling settings scrollView.hasVerticalScroller = true scrollView.hasHorizontalScroller = true scrollView.scrollerStyle = .overlay // Modern overlay scrollers work better with responsive scrolling scrollView.automaticallyAdjustsContentInsets = true // Host your SwiftUI content in an NSHostingView let hostingView = NSHostingView(rootView: content) hostingView.autoresizingMask = [.width, .height] // Ensure content resizes with the scroll view scrollView.documentView = hostingView // Enable responsive scrolling by setting the compatibility flag scrollView.setValue(true, forKey: "isCompatibleWithResponsiveScrolling") return scrollView } func updateNSView(_ nsView: NSScrollView, context: Context) { // Update the SwiftUI content when the view refreshes if let hostingView = nsView.documentView as? NSHostingView<Content> { hostingView.rootView = content } } } // Usage Example struct ContentView: View { var body: some View { ResponsiveScrollView { VStack(spacing: 20) { ForEach(0..<100) { index in Text("Item \(index)") .font(.title) .frame(maxWidth: .infinity) .padding() .background(Color.gray.opacity(0.2)) } } .padding() } .frame(maxWidth: .infinity, maxHeight: .infinity) } }
2. Use Introspect to Modify the Underlying NSScrollView
If you prefer a more concise approach without writing a full wrapper, you can use the Introspect library to access the native NSScrollView backing SwiftUI's ScrollView and tweak its settings:
import SwiftUI import Introspect struct ContentView: View { var body: some View { ScrollView { VStack(spacing: 20) { ForEach(0..<100) { index in Text("Item \(index)") .font(.title) .frame(maxWidth: .infinity) .padding() .background(Color.gray.opacity(0.2)) } } .padding() } .introspectScrollView { scrollView in // Enable responsive scrolling scrollView.setValue(true, forKey: "isCompatibleWithResponsiveScrolling") // Optional: Switch to overlay scrollers for a more modern feel scrollView.scrollerStyle = .overlay } .frame(maxWidth: .infinity, maxHeight: .infinity) } }
Key Notes:
- The
isCompatibleWithResponsiveScrollingflag is set via KVC here because it's a class-level property in AppKit, but this works reliably in Xcode 12 beta 4 and macOS Big Sur beta 4. - Overlay scrollers (
scrollerStyle = .overlay) pair better with responsive scrolling, giving you that smooth, fluid experience similar to List.
内容的提问来源于stack exchange,提问作者AlbertUI

