Although my issue has been resolved, I am still waiting for a native SwiftUI solution. This is just a workaround.
NSScrollView() and override the scrollWheel() method to forward vertical scroll events. This answer also forwards vertical scroll events by overriding wantsForwardedScrollEvents(), but it is "too sensitive". Users' fingers cannot scroll precisely horizontally; there will always be a certain distance generated on the y-axis. Therefore, I did not adopt that method, even though it seems to be "less intrusive".class MTHorizontalScrollView: NSScrollView {
var currentScrollIsHorizontal = false
override func scrollWheel(with event: NSEvent) {
if event.phase == NSEvent.Phase.began || (event.phase == NSEvent.Phase.ended && event.momentumPhase == NSEvent.Phase.ended) {
currentScrollIsHorizontal = abs(event.scrollingDeltaX) > abs(event.scrollingDeltaY)
}
if currentScrollIsHorizontal {
super.scrollWheel(with: event)
} else {
self.nextResponder?.scrollWheel(with: event)
}
}
}
NSViewRepresentable to use it in SwiftUI.struct NSScrollViewWrapper<Content: View>: NSViewRepresentable {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeNSView(context: Context) -> NSScrollView {
let scrollView = MTHorizontalScrollView()
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = false
scrollView.verticalScrollElasticity = .none
scrollView.horizontalScrollElasticity = .allowed
scrollView.autohidesScrollers = true
scrollView.drawsBackground = false
let hostingView = NSHostingView(rootView: content)
hostingView.translatesAutoresizingMaskIntoConstraints = false
scrollView.documentView = hostingView
NSLayoutConstraint.activate([
hostingView.topAnchor.constraint(equalTo: scrollView.topAnchor),
hostingView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
hostingView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
hostingView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor)
])
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
if let hostingView = nsView.documentView as? NSHostingView<Content> {
hostingView.rootView = content
}
}
}
View for easier use anywhere.extension View {
@ViewBuilder
func forwardedScrollEvents(_ enabled: Bool = true) -> some View {
if enabled {
NSScrollViewWrapper {
self
.scrollDisabled(true)
.frame(maxWidth: .infinity, alignment: .leading)
}
} else {
self
}
}
}
struct ContentView: View {
var body: some View {
List {
ForEach(0..<5) { index in
Text("\(index) line")
}
ScrollView(.horizontal) {
Text("hello, world! hello, world! hello, world! hello, world! hello, world!\n hello, world! hello, world! hello, world! \n hello, world! hello, world! hello, world! hello, world!")
}.font(.largeTitle)
.forwardedScrollEvents() //this!
ForEach(5..<10) { index in
Text("\(index) line")
}
}
}
}