How to use imperative UIKit navigation with SwiftUI-view and Load-then-navigate pattern

Hi there, another one previously slightly on the fence regarding introducing TCA to the team overall, but who is now embracing it :heart:

I would, however, like to have a UIKit shell calling into SwiftUI-views and navigating between them so as to be able to use the views inside different flows, i.e. not using the NavigationLink

I have a view in which the user can tap a button, the api is called, and on callback call a function that navigates in UIKit. This is what is called Load-then-navigate in the TCA case studies, however my use case seems to be in between the UIKit way and the SwiftUI way.

I need to observe an optional property of the State and navigate when it changes from nil to non-nil, but if using the IfLetStore I have to return a View. This could maybe be hacked by returning EmptyView, and calling a navigate function as a side effect but it seems wrong.

Otherwise I could subscribe to the publisher of the store and navigate on change from nil to non-nil, but then I would need to save the Cancellable, but.as the View is a struct it would need to be saved either in global namespace or in the State itself.

Would the solution be a ViewModifier, that checks for selected piece of state changing in a specific way and calls imperative code? Does this already exist?

3 Likes

There's an ifLet method on Store for imperative navigation, which I think is what you're looking for:

An example of its use in UIKit case studies:

And TicTacToe's UIKit demo:

There's also an example of an IfLetStoreController:

This doesn't ship with the library but can be copied and pasted into your project if you need something like it.

1 Like

@stephencelis Thank you so much, indeed this is the pattern I am looking for! However I need to use this in SwiftUI, only calling out to UIKit for navigation, so my concerns with this approach is that I inside the SwiftUI view then need to store the Cancellable. Where would that go? A natural place would be saving it in State but it feels a little like it does not belong there.

For reference the way I'd like to call this would be along the lines of

Button(viewStore.topButtonTitle, action: {
                        viewStore.send(.tappedTopButton)
                        IfLetStore(
                            self.store.scope(
                                state: { $0.optionalPaymentResponse }, action: PricePickerAction.optionalPaymentResponse),
                            then: { store in self.coordinator.navigate(store.optionalPaymentResponse) }
                        )
                    })

Not that it has to be this, but I hope it conveys what I am trying to do, which is calling out to some instance of an object that handles routing on change from nil to non-nil.

Alternatively I guess the target of the navigation could be injected, but that would mix navigation paradigmes.

Maybe I am in a peg and hole mismatch situation?