5

I have a sheet view that is presented when a user clicks a button as shown in the parent view below:

struct ViewWithSheet: View {
    @State var showingSheetView: Bool = false
    @EnvironmetObject var store: DataStore()

    var body: some View {
        NavigationView() {
            ZStack {
               Button(action: { self.showingSheetView = true }) {
                   Text("Show sheet view")
               }
            }
            .navigationBarHidden(true)
            .navigationBarTitle("")
            .sheet(isPresented: $showingSheetView) {
                SheetView(showingSheetView: self.$showingSheetView).environmentObject(self.dataStore)
            }
        }
    }
}

In the sheet view, when a user clicks another button, an action is performed by the store that has a completion handler. The completion handler returns an object value, and if that value exists, should dismiss the SheetView.

struct SheetView: View {

    @Binding var showingSheetView: Bool

    @EnvironmentObject var store: DataStore()

    //@Environment(\.presentationMode) private var presentationMode

    func create() {
        store.createObject() { object, error in 
           if let _ = object {
               self.showingSheetView = false
               // self.presentationMode.wrappedValue.dismiss()
           }
        }
    }

    var body: some View {
        VStack {
            VStack {
                HStack {
                    Button(action: { self.showingSheetView = false }) {
                        Text("Cancel")
                    }
                    
                    Spacer()
                    
                    Spacer()
                    
                    Button(action: { self.create() }) {
                        Text("Add")
                    }
                }
                .padding()
            }
        }
    }
}

However, in the create() function, once the store returns values and showingSheetView is set to false, the sheet view doesn't dismiss as expected. I've tried using presentationMode to dismiss the sheet as well, but this also doesn't appear to work.

3 Answers 3

2

I found my issue, the sheet wasn't dismissing due to a conditional in my overall App wrapping View, I had an if statement that would show a loading view on app startup, however, in my DataStore I was setting it's fetching variable on every function call it performs. When that value changed, the view stack behind my sheet view would re-render the LoadingView and then my TabView once the fetching variable changed again. This was making the sheet view un-dismissable. Here's an example of what my AppView looked like:

struct AppView: View {

    @State private var fetchMessage: String = ""

    @EnvironmentObject var store: DataStore()

    func initializeApp() {
        self.fetchMessage = "Getting App Data"
        store.getData() { object, error in 
            if let error = error {
                self.fetchMessage = error.localizedDescription
            }

            self.fetchMessage = ""
        }
    }

    var body: some View {
        Group {
            ZStack {
                //this is where my issue was occurring
                if(!store.fetching) {
                    TabView {
                        Tab1().tabItem {
                            Image(systemName: "tab-1")
                            Text("Tab1")
                        }

                        Tab2().tabItem {
                            Image(systemName: "tab-2")
                            Text("Tab2")
                        }

                        //Tab 3 contained my ViewWithSheet() and SheetView()
                        Tab3().tabItem {
                            Image(systemName: "tab-3")
                            Text("Tab3")
                        }
                    }
                } else {
                    LoadingView(loadingMessage: $fetchMessage)
                }
            }
        }.onAppear(perform: initializeApp)
    }
}

To solve my issue, I added another variable to my DataStore called initializing, which I use to render the loading screen or the actual application views on first .onAppear event in my app. Below is an example of what my updated AppView looks like:

struct AppView: View {

    @State private var fetchMessage: String = ""

    @EnvironmentObject var store: DataStore()

    func initializeApp() {
        self.fetchMessage = "Getting App Data"
        store.getData() { object, error in 
            if let error = error {
                self.fetchMessage = error.localizedDescription
            }

            self.fetchMessage = ""
            //set the value to false once I'm done getting my app's initial data.
            self.store.initializing = false
        }
    }

    var body: some View {
        Group {
            ZStack {
                //now using initializing instead
                if(!store.initializing) {
                    TabView {
                        Tab1().tabItem {
                            Image(systemName: "tab-1")
                            Text("Tab1")
                        }

                        Tab2().tabItem {
                            Image(systemName: "tab-2")
                            Text("Tab2")
                        }

                        //Tab 3 contained my ViewWithSheet() and SheetView()
                        Tab3().tabItem {
                            Image(systemName: "tab-3")
                            Text("Tab3")
                        }
                    }
                } else {
                    LoadingView(loadingMessage: $fetchMessage)
                }
            }
        }.onAppear(perform: initializeApp)
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

I have basically the same issue right now; my sheet is a registration view called from the login view. When registered, the underlying view of my registration sheet changes from login view to "signed in" view, which makes my sheet undismissable - I think due to the fact that the "body" of my login view, on which the sheet is called, is not updated anymore, and so the sheet is not dismissed. Very annoying that a sheet cannot dismiss itself.
1

Try to do this on main queue explicitly

func create() {
    store.createObject() { object, error in 
       if let _ = object {
           DispatchQueue.main.async {
              self.showingSheetView = false
           }
       }
       // think also about feedback on else case as well !!
    }
}

1 Comment

I tried your suggestion, but unfortunately the view still doesn't dismiss. object definitely has a value and the showingSheetView variable's value does get set to false, however the sheet still does not dismiss.
0

Want to see something hacky that worked for me? Disclaimer: Might not work for you and I don't necessarily recommend it. But maybe it'll help someone in a pinch.

If you add a NavigationLink AND keep your fullScreenCover, then the fullscreen cover will be able to dismiss itself like you expect.

Why does this happen when you add the NavigationLink to your View? I don't know. My guess is it creates an extra reference somewhere.

Add this to your body, and keep your sheet as it is:

NavigationLink(destination: YOURVIEW().environmentObjects(), isActive: $showingSheetView) {}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.