arkivanov / Decompose

Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing (navigation) and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.)
https://arkivanov.github.io/Decompose
Apache License 2.0
2.23k stars 84 forks source link

Swiftui push pop navigation example #71

Closed Reedyuk closed 2 years ago

Reedyuk commented 2 years ago

The decompose examples are great but they don't demonstrate a typical navigation stack of pushing and popping.

The counter and master detail examples show a 'replace' navigation but doesn't show a push transition - this is probably the most common navigation pattern used within ios/swiftui

Maybe you could take inspiration of this: https://quickbirdstudios.com/blog/coordinator-pattern-in-swiftui/

Also would be cool to see an example using deeplink navigation, where a user can click an item in the list and this fires off a deeplink to push a view in the navigation.

arkivanov commented 2 years ago

Hello. The Counter sample does demonstrate push/pop, including nested back stack.

There is also the TodoApp example, which also demonstrates push/pop.

Both examples have iOS SwiftUI attached.

Reedyuk commented 2 years ago

Here is a code snippet from the counter sample:

var body: some View {
        let activeChild = self.routerState.value.activeChild.instance

        return VStack(spacing: 8) {
            CounterView(self.counterRoot.counter)

            Button(action: self.counterRoot.onNextChild, label: { Text("Next Child") })

            Button(action: self.counterRoot.onPrevChild, label: { Text("Prev Child") })
                .disabled(!activeChild.isBackEnabled)

            CounterInnerView(activeChild.inner)
        }
    }

From what i can see, it's a straight replace, nothing to do with pushing and popping transition.

I was half expecting to see a NavigationView being used in swiftui

arkivanov commented 2 years ago

@Reedyuk Exactly, the same applicable for any other UI - Jetpack Compose, React, etc. The source of truth for the navigation is Decompose's Router. The Router exposes the navigation state, where there is an active child and a back stack (inactive children). The UI is only concerned about rendering whatever is currently active. So no need to use the NavigationView, etc. The UI subscribes to the Router state changes using the Router.state: Value<RouterState> property -> the Router pushes a new child into the stack -> the Router state is updated -> the UI receives the updated state and re-renders.

The tricky part here is animating transitions between screens. Decompose provides APIs for Jetpack Navigation. But there is nothing out-of-the-box for SwiftUI. This was discussed in #21. The explanation of why it's not possible to include animation APIs for SwiftUI into Decompose can be found in the "old" repo - badoo/Decompose/issues/228. So currently, devs have to do something on their own for animations, something similar to what we have for Jetpack Compose.

antoniusnaumann commented 2 years ago

Using a NavigationView can be desirable, especially when working on a SwiftUI project which targets i.e. both iOS, iPadOS and macOS to achieve visual consistency for each platform. And as far as I know, this is possible while using Decompose for Routing.

@Reedyuk From my understanding, using a NavigationView just for presentation together with a Decompose router as single source of truth should be possible by implementing a custom binding and passing it as the selection parameter in this NavigationLink constructor. Only necessary addition compared to this navigation example needed that I can see so far would be adding a tag-Property to Root.Child since NavigationLink uses tags to identify the currently selected element.

I am currently playing around with Decompose in a personal project and can add a code example if needed once I tested this approach.

arkivanov commented 2 years ago

@antoniusnaumann Thanks for the useful information. Please don't hesitate do add any code examples, as it may help other devs.

PS: converted this issue to a discussion.