4

I have a TabView with two tabs in a SwiftUI lifecycle app, one of them has complex view structure: NavigationView with a lot of sub-views inside, i.e.: NavigationLinks and their DestinationViews are spread on multiple levels down the view tree, each sub-view on its own is another view hierarchy with sheets and / or other DestinationViews. At some point inside this hierarchy, I want to reset the TabView to its original state which is displaying the first most view, so the user can restart their journey right at that state, as they were to open the app for the first time, so it's kinda impossible to track down something like isActive & isPresented bindings to pop-off or dismiss the views and sheets.

I thought of wrapping the TabView inside another view: RootView in an attempt to find an easy way to recreate that TabView from scratch or something like refreshing / resetting the TabView, but couldn't find a clew on how to do it.

Here's my code snippet:

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}

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

struct ContentView: View {
    var body: some View {
        TabView { // <-- I need to reset it to its original state
           View1() // <---- this view has complex view hierarchy
            .tabItem {
                Text("Home")
                
            }.tag(0)
            
            View2()
            .tabItem {
                Text("Settings")
            }.tag(1)
        }
    }
}

p.s. I'm not looking for "popping off the view to root view", because this can't be done when there are many active NavigationLink destinations where the user might open one of the sheets and start a new navigation journey inside the sheet.

****** UPDATE ******

I've created a new Environment value to hold a boolean that should indicate whether the TabView should reset or not, and I've tracked every isPresented and isActive state variables in every view and reset them once that environment value is set to true like this:

struct ResetTabView: EnvironmentKey {
static var defaultValue: Binding<ResetTabObservable> = .constant(ResetTabObservable())
 }
 extension EnvironmentValues {
var resetTabView: Binding<ResetTabObservable> {
    get { self[ResetTabView.self] }
    set { self[ResetTabView.self] = newValue }
   }
 }

 class ResetTabObservable: ObservableObject {
    @Published var newValue = false
}

in every view that will present a sheet or push a new view I added something like this:

struct View3: View {
    @State var showSheet = false
    @Environment(\.resetTabView) var reset
    
    var body: some View {
        Text("This is view 3")
        Button(action: {
            showSheet = true
        }, label: {
            Text("show view 4")
        })
        .sheet(isPresented: $showSheet) {
            View4()
        }
        .onReceive(reset.$newValue.wrappedValue, perform: { val in
            if val == true {
                showSheet = false
            }
        })
    }
}

and in the last view (which will reset the TabView) I toggle the Environment value like this:

struct View5: View {
    @Environment(\.resetTabView) var reset
    
    var body: some View {
        VStack {
            Text("This is view 5")
            Button(action: {
                reset.newValue.wrappedValue = true
            }, label: {
                Text("reset tab view")
            })
        }
    }
}

This resulted in awkward dismissal for views:

enter image description here

JAHelia
  • 6,934
  • 17
  • 74
  • 134
  • Does this answer your question https://stackoverflow.com/a/67015765/12299030? (see place about *reset to root*) – Asperi Apr 11 '21 at 15:11
  • Not really what i want, i added a p.s. in the question above that i’m not looking for “dismissing or popping to root view” – JAHelia Apr 11 '21 at 18:27

1 Answers1

1

What i do for this is i make all my presentation bindings be stored using @SceneStorage("key") (instead of @State) this way they not only respect state restoration ! but you can also access them throughout your app easily by using the same key. This post gives a good example of how this enables the switching from Tab to Sidebar view on iPad.

I used this in my apps so if i have a button or something that needs to unwind many presentations it can read on all of these values and reset them back to wanted value without needing to pass around a load of bindings.

Matthaus Woolard
  • 2,310
  • 11
  • 13
  • this won't reset a ton of opened pages back to the first tab view, I guess the best way to achieve this is to create a brand new `RootView` that holds a new `TabView` – JAHelia Apr 12 '21 at 06:20
  • it will if you have a `SceneStorage` for each of those presentations, then in your button you can reset these all to the default state. (you do need to set all of them explicitly through yes) – Matthaus Woolard Apr 12 '21 at 07:56