JAHelia
JAHelia

Reputation: 7932

SwiftUI: resetting TabView

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

Upvotes: 5

Views: 1573

Answers (1)

Matthaus Woolard
Matthaus Woolard

Reputation: 2408

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.

Upvotes: 1

Related Questions