Why does this fix my Navigation in iOS 16.4?

I've been trying to figure out why my navigation is broken in iOS 16.4 It worked in 16.3. I have been able to fix the problem, however, I would like to know why this fixes it. I don't understand.

The problem I was trying to fix is this: Whenever I appended an item to the NavigationPath, the new view never got pushed (it did in iOS 16 - 16.3). Nothing happened in the view. Then, when I tap on a normal navigation link as in NavigationLink(value: Destinations.goal), the previously appended path would push, followed by a push of the view specified for the NavigationLink's value in .navigationDestination(for:).

In MainGoalView, the line navItem.navigationPath.append(Destinations.formStatus) had no effect when I tap on "Submitted Forms". However, if I tapped "Submitted Forms", then tap on "Information", both links get executed so that I see the view "Information". Then tapping the Back button, I see "Form Status". (I also saw "Form Status" momentarily before "Information" was pushed.)

struct MainGoalView: View {
    @EnvironmentObject var navItem: AppNavigationItem

    enum Destinations: Hashable, Codable { case formStatus, information }

    @State private var isShowingAlert = false
    @State private var isShowingForms = false
    @State private var navigationPath = NavigationPath()

    var body: some View {
        List {
            Section {
                UpcomingGoalsView()
            } header: {
                Text("Upcoming")
            }

            Section {
                Text("Form Summary")
            } header: {
                SectionActionHeader(title: "Submitted Forms", actionText: "View") {
                    navItem.navigationPath.append(Destinations.formStatus)		// <- HERE
                }
            }

            Section {
                NavigationLink(value: Destinations.information) {
                    Text("Information")
                }
            } header: {
                Text("Information")
            }
        }
        .listStyle(.insetGrouped)
        .navigationDestination(for: Destinations.self) { value in
            switch value {
                case .formStatus: Text("Form Status")
                case .information: Text("Information")
            }
        }
    }
}

I finally tracked the problem down to the code that sets up my tab bar. Before looking at that code, here is the code code that configures the tab bar. The AppNavigator class contains a list of AppNavigationItems that defines each tab of the tab bar. (This was set up so it could be used for a tab bar, or for another interface such as a NavigationSplitView)

enum AppNavigatorType {
    case insights
    case people
}

class AppNavigator: ObservableObject {
    static let shared = AppNavigator()
    static let defaultStartTab = AppNavigatorType.insights
    @Published var selectedNavigatorType: AppNavigatorType? = AppNavigator.defaultStartTab

    var items: [AppNavigationItem] = [
        AppNavigationItem(AnyView(InsightsView()),          type: .insights, title: "Insights", systemImageName: "chart.bar"),
        AppNavigationItem(AnyView(Text("Not Implemented")), type: .people,   title: "People",   systemImageName: "person"),
    ]
}

class AppNavigationItem: ObservableObject, Identifiable {

    @Published var navigationPath = NavigationPath()

    var id: String { title }
    var view: AnyView
    var type: AppNavigatorType?
    var title: String
    private var systemImageName: String
    var image: Image

    init(_ view: AnyView, type: AppNavigatorType, title: String, systemImageName: String) {
        self.view = view
        self.type = type
        self.title = title
        self.systemImageName = systemImageName
        self.image = Image(systemName: systemImageName)
    }
}

Finally, here is the code where I was able to correct the problem, but I'm not sure why it fixes it.

First, here is the version that doesn't work correctly. I was never quite sure as to why I had to use the $ to get it to compile, but it all worked in iOS 16 - 16.3

public struct MainTabView: View {
    @EnvironmentObject var navigator: AppNavigator

    public var body: some View {
        TabView {
            ForEach ($navigator.items) { $navItem in		// <- Why the $
                NavigationStack(path: $navItem.navigationPath) {
                    let _: Int = {  print(navItem.navigationPath); return 0 }()
                    navItem.view
                }
                .environmentObject(navItem)
                .tabItem {
                    Text(navItem.title)
                    navItem.image
                        .environment(\.symbolVariants, navigator.selectedNavigatorType == navItem.type ? .fill : .none)
                }
                .tag(navItem.type)
                .navigationTitle(navItem.title)
                .id(navItem.id)
            }
        }
    }
}

Unfortunately, it doesn't work in iOS 16.4, as already described.

I changed the above code for MainTableView to the following, and now it works in iOS 16.4. I just moved the code inside the ForEach into another View.

I would love someone smarter than me to help me understand why.

public struct MainTabView: View {
    @EnvironmentObject var navigator: AppNavigator

    public var body: some View {
        TabView {
            ForEach (navigator.items) { navItem in		// <- Ah! $ not needed now.
                TabBarRootView(navItem: navItem)
            }
        }
    }
}

struct TabBarRootView: View {
    @EnvironmentObject private var navigator: AppNavigator

    @ObservedObject var navItem: AppNavigationItem	  // <- navItem is an ObservableObject, and this is observing it.

    var body: some View {
        NavigationStack(path: $navItem.navigationPath) {		// <- Pass the binding to the published navigationPath.
            let _: Int = {  print(navItem.navigationPath); return 0 }()
            navItem.view
        }
        .environmentObject(navItem)
        .tabItem {
            Text(navItem.title)
            navItem.image
                .environment(\.symbolVariants, navigator.selectedNavigatorType == navItem.type ? .fill : .none)
        }
        .tag(navItem.type)
        .navigationTitle(navItem.title)
        .id(navItem.id)
    }
}

I believe the problem is in the fact that the navigator object, which is an ObservableObject, contains an array items, which are also ObservableObjects. This is nesting ObservableObjects inside ObservableObjects. The $ references the outer ObservableObject, but there isn't a way to reference the nested ObservableObject.

The code that works grabs the inner ObservableObject, an item, and then can pass it as the new view which expects the ObservableObject.

ForEach ($navigator.items) { $navItem in // <- Why the $

Why ? Because you have a Binding ($) in ForEach

In the other one, there is no Binding.

You use

        .environmentObject(navItem)

So, I think you should declare navItem as EnvironmentObject

Why does this fix my Navigation in iOS 16.4?
 
 
Q