Search code examples
swiftuiswiftui-navigationlinkswiftui-navigationview

SwiftUI - TabView/NavigationLink navigation breaks when using a custom binding


I'm having trouble with what I think may be a bug, but most likely me doing something wrong.

I have a slightly complex navigation state variable in my model that I'm using for tracking/setting state between tab and sidebar presentations when multitasking on iPad. That all works fine except in tab mode, once I use a navigation link once I can't seem to use one again, whether the binding is on my tab view or navigation links in a list.

enter image description here

Would really appreciate any thoughts on this, Cheers!

Example

NavigationItem.swift

enum SubNavigationItem: Hashable {
    case overview, user, hobby
}

enum NavigationItem: Hashable {
    case home(SubNavigationItem)
    case settings
}

Model.swift

final class Model: ObservableObject {
    @Published var selectedTab: NavigationItem = .home(.overview)
}

SwiftUIApp.swift

@main
struct SwiftUIApp: App {
    @StateObject var model = Model()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(model)
        }
    }
}

ContentView.swift

struct ContentView: View {
    var body: some View {
        AppTabNavigation()
    }
}

AppTabNavigation.swift

struct AppTabNavigation: View {
    @EnvironmentObject private var model: Model

    var body: some View {
        TabView(selection: $model.selectedTab) {
            NavigationView {
                HomeView()
            }
            .tabItem {
                Label("Home", systemImage: "house")
            }
            .tag(NavigationItem.home(.overview))

            NavigationView {
                Text("Settings View")
            }
            .tabItem {
                Label("Settings", systemImage: "gear")
            }
            .tag(NavigationItem.settings)
        }
    }
}

HomeView.swift

I created a binding here because selection required an optional <NavigationItem?> not

struct HomeView: View {
    @EnvironmentObject private var model: Model
    
    var body: some View {
        let binding = Binding<NavigationItem?>(
            get: { 
                model.selectedTab 
            },
            set: {
                guard let item = $0 else { return }
                model.selectedTab = item
            }
        )
        
        List {
            NavigationLink(
                destination: Text("Users"),
                tag: .home(.user),
                selection: binding
            ) {
                Text("Users")
            }
            NavigationLink(
                destination: Text("Hobbies"),
                tag: .home(.hobby),
                selection: binding
            ) {
                Text("Hobbies")
            }
        }
        .navigationTitle("Home")
    }
}

Second Attempt

I tried making the selectedTab property optional as @Lorem Ipsum suggested. Which means I can remove the binding there. But then the TabView doesn't work with the property. So I create a binding for that and have the same issue but with the tab bar!

enter image description here


Solution

  • Make the selected tab optional

    @Published var selectedTab: NavigationItem? = .home(.overview)
    

    And get rid of that makeshift binding variable. Just use the variable

    $model.selectedTab
    

    If the variable can never be nil then something is always selected IAW with that makeshift variable it will just keep the last value.