pointfreeco / swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
https://www.pointfree.co/collections/composable-architecture
MIT License
12.24k stars 1.42k forks source link

Tree-based navigation deeplinking shows mostly blank screens #2485

Closed TheEskil closed 11 months ago

TheEskil commented 11 months ago

Description

When deep linking using tree-based navigation, with PresentationState/PresentationAction and View.navigationDestination it sends you to the right place, but after the first destination everything falls apart, i.e. blank screens.

Checklist

Expected behavior

I expected each new destination to work as if the user navigated there themselves (which does work splendidly).

Actual behavior

Destinations past the first one only shows blank screens. Only the back button is visible, and sometimes the navigation title, but not always.

Steps to reproduce

I've made a minimal reproduction available here: https://github.com/TheEskil/TCA-TreeBasedNavBug

The Composable Architecture version information

1.2.0

Destination operating system

iOS 17

Xcode version information

Version 15.0 (15A240d)

Swift Compiler version information

swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx13.0
x-0o0 commented 11 months ago

Additional comment for the issue

According to the logs that are provided by _printChanges(), at the first, the states are well-assigned but immediately .destination(.dismiss) is called from the parent reducer via navigationDestination method:

received action:
  AppReducer.Action.secondTapped
- AppReducer.State(_destination: nil)
+ AppReducer.State(
+   _destination: .first(
+     FirstReducer.State(
+       _destination: .second(
+         SecondReducer.State(_destination: nil)
+       )
+     )
+   )
+ )

received action:  // ❌ unexpected action is called
  AppReducer.Action.destination(
    .presented(
      .first(
        .destination(.dismiss)
      )
    )
  )
  AppReducer.State(
    _destination: .first(
-     FirstReducer.State(
-       _destination: .second(
-         SecondReducer.State(_destination: nil)
-       )
-     )
+     FirstReducer.State(_destination: nil)
    )
  )
stephencelis commented 11 months ago

I believe this is a vanilla SwiftUI bug with navigationDestination, though if you can show it working there we can look into if it's a TCA bug.

We provide a workaround in our SwiftUINavigation package, but have decided against doing so in TCA, since it introduces a flakey code path that could break at any time when Apple fixes things. There's recent discussion here about how the modifier has regressed in iOS 17, and a possible workaround you can bring to your TCA project: https://github.com/pointfreeco/swiftui-navigation/discussions/121

I'm going to convert this to a discussion, since it appears to be an Apple bug and not a bug with the library.