@PresentationState updated from a child action doesn't navigate to destination view

Hi everyone,

I'm displaying a list of items that pushes another view when a row is tapped using @PresentationState and .navigationDestination but I noticed that sometimes when tapping a row nothing happens until a second tap is done like the following image.

presentation-state

Below I pasted a simplified version of what I'm trying to build that has the random behavior. Since I want to do different things when different parts of a row are tapped it makes sense to me to define the tap actions in the reducer (Row) of each row. Then in my list reducer (ItemsList) I simply set the PresentationState when a specific part of the row is tapped.

To replicate the issue I go back and forward tapping different rows and eventually some of the taps will fail until another tap which could be in other part of the screen is done.

import ComposableArchitecture
import SwiftUI

@main
struct Application: App {
    let items = ItemsList.State(
        items: [
            Row.State(name: "Item 1"),
            Row.State(name: "Item 2"),
            Row.State(name: "Item 3"),
        ]
    )
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                ItemsListView(
                    store: Store(
                        initialState: items,
                        reducer: { ItemsList() }
                    )
                ).navigationTitle("Presentation state")
            }
        }
    }
}

struct Row: Reducer {
    struct State: Equatable, Identifiable {
        var id = UUID()
        var name: String
    }

    enum Action {
        case specificAreaTapped
    }

    var body: some Reducer<State, Action> { EmptyReducer() }
}

struct RowView: View {
    let store: StoreOf<Row>

    var body: some View {
        WithViewStore(store, observe: { $0 }) { viewStore in
            HStack {
                Text(viewStore.name)
                Spacer()
                Image(systemName: "chevron.right")
            }
            .contentShape(Rectangle())
            .onTapGesture {
                viewStore.send(.specificAreaTapped)
            }
        }
    }
}

struct ItemsList: Reducer {
    struct State: Equatable {
        var items: IdentifiedArrayOf<Row.State>
        @PresentationState var push: Row.State?
    }

    enum Action {
        case row(Row.State.ID, Row.Action)
        case push(PresentationAction<Row.Action>)
    }

    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case .row(let id, .specificAreaTapped):
                guard let tappedItem = state.items[id: id] else {
                    return .none
                }
                print("\(tappedItem.name) tapped")
                state.push = tappedItem
                return .none
            case .push:
                return .none
            }
        }
        .forEach(\.items, action: /Action.row) { Row() }
        .ifLet(\.$push, action: /Action.push) { Row() }
    }
}

struct ItemsListView: View {
    let store: StoreOf<ItemsList>

    var body: some View {
        WithViewStore(store, observe: \.items) { viewStore in
            VStack {
                ForEachStore(store.scope(state: \.items, action: ItemsList.Action.row)) { store in
                    RowView(store: store).padding()
                }
                Spacer()
            }
            .navigationDestination(
                store: store.scope(state: \.$push, action: { .push($0) })
            ) { store in
                WithViewStore(store, observe: { $0 }) { detailViewStore in
                    Text(detailViewStore.name)
                }
            }
        }
    }
}

For my tests I tried in iOS 16, 17 with Xcode 14 and 15 beta-5 respectively and swift-composable-architecture v1.0.0 and v1.2.0 which was released today :tada:, with the same results.

Does anyone know what I'm doing wrong here or a reason for this behavior? :thinking:

Additionally, I have some interesting findings:

  • If I move the tap action from the child reducer to the list reducer everything works as expected, this is ok if there is only one tap action for the entire row.
  • When adding _printChanges to ItemsList reducer to debug state's changes everything works as usual, the error is no longer there :man_shrugging:t2:

Hey @bgonzales

I've copied and pasted your code into a new Xcode 14.3.1 (14E300c) project, and it worked like a charm... :thinking:

I've recorded a video:
RocketSim_Recording_iPhone_14_Pro_2023-08-23_09.22.43

Here are the printed changes:

Item 1 tapped
Item 2 tapped
Item 3 tapped
Item 2 tapped
Item 1 tapped

Hey @otondin thanks for checking this, I have the same version of Xcode and I'm still seeing the issue in all my simulators but sometimes it takes me more time to replicate the bug :smiling_face_with_tear:

Since you didn't have the same issue I ran the app in a real device and looks like everything is working just fine. If no one else reports something similar I will assume it is a simulator thing and continue my migration to TCA.

1 Like

@bgonzales I see! It seems pretty much a Simulator issue. Have you maybe already tried to reset your Simulator by "erase all content and settings"?

Oh I forgot to try that @otondin but now I did it and still having that behavior :smiling_face_with_tear:. I will take some time later to actually figure out what's going on and if I found something interesting I will post it here, luckily that is the only odd thing I got during my TCA migration.

I see! Have you tried to reproduce this - let's say Simulator issue - on Xcode 15 Beta for a chance? :thinking:

Hey @otondin yep I tried that before using vbeta-5 with the same result, fortunately I misread your last reply and I downloaded iOS 15 simulator :sweat_smile:. I had to replace .navigationDestination with NavigationLinkStore though but the issue was no longer there!

Initially I thought that was the reason behind it but I watched the episodes about navigationDestination again and I didn't have a strong reason to think that was the problem so I reinstalled my other simulators and that worked as a charm. I never experienced an issue with the simulators before, I guess there is always a first time.

I really appreciate all your help @otondin :tada:

1 Like