frzi / swiftui-router

Path-based routing in SwiftUI
MIT License
900 stars 43 forks source link

Best practice with back swipe gesture #28

Closed koraykoska closed 2 years ago

koraykoska commented 2 years ago

Obviously, without Animations, SwiftUIRouter does not provide native-like back swipes in the navigation hierarchy. Do you know any good way to replicate those edge pan swipes to feel native? In iOS, swipe is a really important feature and ignoring it is not really an option. That being said using path-based routing offers great benefits over the traditional routing model in SwiftUI. Combining both would be ideal.

Currently, I am using a somewhat hacky solution which is really not nice at all compared with the native feel of back swipes:

struct DefaultBackSwipeGesture: ViewModifier {
    @EnvironmentObject private var navigator: Navigator

    @GestureState private var dragOffset = CGSize.zero

    func body(content: Content) -> some View {
        content.gesture(DragGesture().updating($dragOffset, body: { (value, state, transaction) in
            if(value.startLocation.x < 20 && value.translation.width > 100) {
                navigator.goBack(total: 1)
            }
       }))
    }
}

extension View {
    func defaultBackSwipeGesture() -> some View {
        return modifier(DefaultBackSwipeGesture())
    }
}

as well as the animation ViewModifier as in your example in the docs.

This leads to a fake swipe where the animation transition to the last View happens once you have done a swipe far enough starting from the left edge.
This is obviously not a real swipe, which means you can't stop at some point or swipe (drag) back and forth a little bit without releasing your finger. It just starts the animation at some points and that's it.

If you don't look closely that's enough. But it's certainly not the best ever user experience. Is there a way to combine the real NavigationView back swipes with this library at all? I imagine replicating it from scratch is too much work for too little outcome. Can we somehow reuse/abuse NavigationView exactly for that?

frzi commented 2 years ago

Sad to say I'm unaware of a method or trick to implement something that fully mimicks iOS' native swipe-to-go-back UX, or to use SwiftUI's NavigationView. The big challenge here is that SwiftUIRouter is unable to render both the current and previous path simultaneously. And it's unaware of the states of the NavigationView/NavigationLinks.

For one app I did implement something that's somewhere between the native UX and your example: When swiping from the left side, the view moves along with the user's finger. Except instead of revealing the previous view - like in iOS - it just reveals a black background. When the user's finger reaches a certain point it commits the navigation. (However, rather than doing navigator.goBack() like in your example, my app does navigator.navigate("..") to go up the path hierarchy, rather than back in history). And the animation as described in the Animating Routes doc takes over.

It's not 100% the same as one might expect from an iOS app - but considering the app deviates a lot from iOS' default UI design anyway, it does the job. 😛

At one point I did come up with the idea to potentially use two Router views to re-create the NavigationView and potentially render two paths simultaneously. But so far have yet the time (and desire) to work out the idea. (And truth be told, I think is far too much hassle than it's worth)

I'd love to see more concepts and implementations from other developers. If anyone has better ideas, please share them! 😄