iosswiftswiftuienvironmentobjectmodal-view

SwiftUI: Close modal sheet programmatically from a function inside an Environment Object


I've a main view that displays a modal sheet. As I've multiple modals, I use enum and State to control which sheet is presented.

@State var activeSheet: Sheet?

Inside my MainView:

    Button(action: {
        activeSheet = .settings
    }, label: {
        Text(“Button”)
    })

   .sheet(item: $activeSheet) { sheet in
        switch sheet {
        case .info:
          InfoView()
        case .settings:
          SettingsView()
        }
      }

SettingsView:

struct SettingsView: View {
    @Environment(\.presentationMode) private var presentationMode
    @EnvironmentObject var model: MainModel
    
    var body: some View {
        Button("Action") {
            model.myFunction()
        }
    }
}

In my InfoView-sheet I have a button that calls a function inside an EnvironmentObject. How do I dismiss the sheet once the function has completed in the EnvironmentObject?

Every view is linked to the same EnvironmentObject btw.

Thanks!


Solution

  • Depending for which min iOS version you are deploying, you have two options:

    If your minimum iOS version is <= iOS 14, in both InfoView and SettingsView use system environment \.presentationMode.

    struct SettingsView: View {
        @EnvironmentObject var model: MainModel
        @Environment(\.presentationMode) private var presentationMode
    
        var body: some View {
            Button("Dismiss") {
    
                /// ... Do something here prior to dismissing the sheet.
                model.MyFunction(email: email, password: password) {
                    presentationMode.wrappedValue.dismiss()
                }
            }
        }
    }
    

    On your minimum iOS version is iOS 15, in both InfoView and SettingsView use system environment \.dismiss.

    struct SettingsView: View {
        @EnvironmentObject var model: MainModel
        @Environment(\.dismiss) private var dismiss
    
        var body: some View {
            Button("Dismiss") {
    
                /// ... Do something here prior to dismissing the sheet.
                model.MyFunction(email: email, password: password) {
                    dismiss()
                }
            }
        }
    }
    

    And MainModel class:

    class MainModel: ObservableObject {
        func myFunction(email: String, password: String, onSuccess: (() -> Void)? = nil) {         
            auth.signIn(withEmail: email, password: password) { [weak self] result, error in
                if result == nil, error != nil {
                     self?.showAlert = true
                } else {
                     guard result != nil, error == nil else { return }
                     /* Success */
                     self?.signedIn = true
                     onSuccess?()
                }
            }
        }
    }
    

    Alternative solution:

    If your minimum deployment target is >= iOS 14, you can listen to changes of your environment object properties like this:

    struct SettingsView: View {
        @EnvironmentObject var model: MainModel
        @Environment(\.dismiss) private var dismiss
    
        var body: some View {
            Button("Dismiss") {
                model.MyFunction(email: email, password: password)
            }
            .onChange(of: model.signedIn) { signedIn in
                if signedIn {
                    dismiss()
                }
            }
        }
    }