Search code examples
swiftuimaster-detailtabbed-view

SwiftUI: MasterDetailView within in Tabbed View loses "State"


I have a MasterDetailView within a Tabbed View. If the user tabs the MasterDetailView and selects an entry in the master view, the detail is presented in the detail view. After selecting another tab and switching back to the MasterDetailView, the detail is no longer selected - the MasterDetailView completely loses its state like is is completely rendered.

private let dateFormatter: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .medium
    dateFormatter.timeStyle = .medium
    return dateFormatter
}()

struct MasterDetailView: View {
    @State private var dates = [Date]()
    var body: some View {
        NavigationView {
            MasterView(dates: $dates)
                .navigationBarTitle(Text("Master"))
                .navigationBarItems(
                    leading: EditButton(),
                    trailing: Button(
                        action: {
                            withAnimation { self.dates.insert(Date(), at: 0) }
                        }
                    ) {
                        Image(systemName: "plus")
                    }
                )
            DetailView()
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

struct MasterView: View {
    @Binding var dates: [Date]

    var body: some View {
        List {
            ForEach(dates, id: \.self) { date in
                NavigationLink(
                    destination: DetailView(selectedDate: date)
                ) {
                    Text("\(date, formatter: dateFormatter)")
                }
            }.onDelete { indices in
                indices.forEach { self.dates.remove(at: $0) }
            }
        }
    }
}

struct DetailView: View {
    var selectedDate: Date?    

    var body: some View {
        Group {
            if selectedDate != nil {
                Text("\(selectedDate!, formatter: dateFormatter)")
            } else {
                Text("Detail view content goes here")
            }
        }.navigationBarTitle(Text("Detail"))
    }
}

struct ContentView: View {      
    @State private var selection = 0
    var body: some View {
        TabView(selection: $selection){
            Text("First View")
                .font(.title)
                .tabItem {
                    VStack {
                        Image("first")
                        Text("First")
                    }
                }
                .tag(0)

            MasterDetailView()
                .tabItem {
                    VStack {
                        Image("second")
                        Text("Master Detail")
                    }
                }
                .tag(1)
        }
    }
}

Is there a way to "reuse" the MasterDetailView when the user selects that tab?

I know I can use @State and @Binding to save and restore the state (like the selected entry in the master view) and in that simple example that might be a solution. But in a complex app - for example when the MasterDetailView includes a deep view hierarchy - it's not useful to manage (save and restore) the complete state of a view.


Solution

  • Variations on this question have been asked several times, and so far, the consensus is that SwiftUI does not support this use case yet. I'm sure it's on their radar, but the more people who ask for this feature, the more likely it will be prioritized for next year's updates.

    Here's an answer where someone got the behavior you're wanting by wrapping UITabBarController for use with SwiftUI.