This gesture mechanism is inspired by the popular Bear MacOS notes app. In my app, there is a 3-column NavigationSplitView. I would like to implement a mechanism such that I could allow users to use 2-fingers swipe gesture (without pressing on touchpad) to toggle between the different NavigationSplitViewVisibility states to open/ close the sidebars:
I followed one of the answers mentioned in SwiftUI: Two-finger swipe ( scroll ) gesture to implement a NSViewPresentable that conforms the ScrollViewDelegateProtocol, then overlays the custom representable scroll view on my NavigationSplitView to detect scrolling gesture:
struct ContentView: View {
struct RepresentableScrollView: NSViewRepresentable, ScrollViewDelegateProtocol{
func updateNSView(_ nsView: ScrollView, context: Context) {
}
func scrollWheel(with event: NSEvent) {
if let scrollAction = scrollAction {
scrollAction(event)
}
NotificationCenter.default.post(name: Notification.Name("scrollView"), object: event)
}
func onScroll(_ action: @escaping (NSEvent) -> Void) -> Self {
var newSelf = self
newSelf.scrollAction = action
return newSelf
}
typealias NSViewType = ScrollView
private var scrollAction: ((NSEvent) -> Void)?
func makeNSView(context: Context) -> ScrollView {
let view = ScrollView()
view.delegate = self;
return view
}
}
var scrollView: some View {
RepresentableScrollView()
...
}
var body: some View {
NavigationSplitView(columnVisibility: $splitViewVisibility) {
...
}
.overlap(scrollView)
}
}
This works fine to detect scrolling gesture but the scrolling generates many events all at once. So I decided to publish the scrolling event and throttle it to get only get the last scrolling event within a time frame (inspired by SwiftUI: How to listen mouse wheel scroll event in horizontal scrollView):
struct ContentView: View {
...
@State private var splitViewVisibility: NavigationSplitViewVisibility = .automatic
let scrollViewPublisher = NotificationCenter.default.publisher(for: Notification.Name(rawValue: "scrollView")).throttle(for: .seconds(0.5), scheduler: DispatchQueue.main, latest: true)
var scrollView: some View {
RepresentableScrollView()
.onReceive(scrollViewPublisher) {
output in
let deltaX = (output.object as! NSEvent).scrollingDeltaX
if (deltaX < 0) {
switch (splitViewVisibility) {
case .all:
self.splitViewVisibility = .doubleColumn
case .doubleColumn:
self.splitViewVisibility = .detailOnly
default:
print("Can't swipe left anymore!")
}
} else if (deltaX > 0) {
switch (splitViewVisibility) {
case .detailOnly:
self.splitViewVisibility = .doubleColumn
case .doubleColumn:
self.splitViewVisibility = .all
default:
print("can't swipe right anymore!")
}
}
}
}
With that, I was able to receive the throttled scrolling event to attempt to transition between different states.
However, the end result of it doesn't work as expected. It is still not able to smoothly transition to different visibility states. The behavior either seems stuck or laggy, and I'm not able get to the .detailOnly visibility state at all.
Is there an easier way to implement this 2-finger swipe gesture to toggle between different NavigationSplitView visibility states?
