Bahn-X / swift-composable-navigator

An open source library for building deep-linkable SwiftUI applications with composition, testing and ergonomics in mind
MIT License
580 stars 25 forks source link

Navigation not causing view to appear #74

Closed ZevEisenberg closed 3 years ago

ZevEisenberg commented 3 years ago

Question

What would cause a view not to appear after pushing a screen onto the nav stack?

Problem description

I added debug() to my navigator. On the left is the initial navigation event that happens when the app loads. On the right is after sending a navigation event like this:

environment.navigator.go(to: TabNavigationScreen(), on: screenID)

where screenID is the ID of the screen that has the button. It also happens to be the root screen, i.e. 00000000-0000-0000-0000-000000000000.

image

I don't have a concise code sample to post, and I'll be making a sample project as my next debugging step, but I wanted to post this in case there was something obvious I was missing. How can I get hasAppeared: false to turn into hasAppeared: true?

ZevEisenberg commented 3 years ago

We solved this, but I don't remember how, and I don't think I understood it anyway 😅

onl1ner commented 2 years ago

Hello, we faced with exact same problem, but cannot come up with solution, @ohitsdaniel could you please help us?

ohitsdaniel commented 2 years ago

Could you please provide an example navigation tree in which the problem occurs? The iOS Version and device the problem occurred on might help as well.

@ZevEisenberg is using a custom path builder implementation and we solved it by rewriting parts of it.

onl1ner commented 2 years ago

Oh, I see @ohitsdaniel. So basically when I call .go(to:on:) function nothing happens, but it shows that view has been added to stack, here is our nav tree:

// CheckoutScreen:

Screen(
    content: { (screen: CheckoutScreen) in
        CheckoutView(checkoutState: screen.$checkoutState)
    },
    nesting: {
        AddressListScreen.Builder()
        ReceiversListScreen.Builder()
        PaymentMethodsScreen.Builder()
        PromocodeDescriptionScreen.Builder()
    }
)

// AddressListScreen:

Screen(AddressListScreen.self) {
    AddressListView()
} nesting: {
    AddressScreen.Builder()
}

Specs: iOS 15.0 swift-composable-navigator 0.2.2

KenLPham commented 2 years ago

@onl1ner I had a similar setup to yours (although the variable in the screen wasn't a binding/state) and noticed a similar issue when trying to work with the deeplinker.

Basically on startup the app will show AScreen with a variable value of nil to display a loading screen. The deeplinker will parse the link into the following path [AScreen(value: "hello"), BScreen()]. Since the deeplinker uses replace(path:) it will successfully replace AScreen but fails at presenting BScreen. Parsing the link so that the value of AScreen is nil will present BScreen fine. So it seems the issue is with the value changing.

To get around this I overrode the equal function to only compare the presentationStyle.

extension EstablishmentScreen {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.presentationStyle == rhs.presentationStyle
    }
}

probably not the best way to solve it, but its a good temporary work around

ohitsdaniel commented 2 years ago

Thanks for your input @KenLPham!

My first assumption would also go in a similar direction:

Is $checkoutState optional state that you unwrap in the view layer? I think it's a pretty cool idea to hand down view models/bindings through screens, just wondering how optional state should be filled in the deeplink case.

Maybe it is an issue in the Navigator.Datasource that only emits a new path if there were changes. Is your $checkoutState a reference or a value type? And it is Hashable/Equatable?

I guess we should track this case in a separate issue because it isn't really related to the original poster's question.

KenLPham commented 2 years ago

I made a new issue here: https://github.com/Bahn-X/swift-composable-navigator/issues/79

In my case the value is unwrapped in the view layer and is a value type that is hashable and equatable. This probably wont match most use cases as this view will only ever be nil as a placeholder when opening a link to an app clip. Since the value is immutable it will have to be replaced by the deeplinker. In the case the deeplinker fails to parse the link the path will be replaced with a CScreen