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")
}
}
}
}