mota
mota

Reputation: 5475

How to support programmatic view dismiss on iOS 15 and earlier versions

On iOS 14 and earlier, we import the presentationMode environment variable:

@Environment(\.presentationMode) var presentationMode

and then call self.presentationMode.wrappedValue.dismiss() to dismiss the view.

This has been deprecated on iOS 15, and a new environment variable, dismiss, is introduced:

@Environment(\.dismiss) var dismiss

which we can call directly using dismiss() to achieve the same.

I know we can do the following to call the appropriate dismiss function and support all versions:

if #available(iOS 15, *) {
    self.dismiss()
} else {
    self.presentationMode.wrappedValue.dismiss()
}

But how do I import/define the right environment variable? As trying this doesn't work:

if #available(iOS 15, *) {
    @Environment(\.dismiss) var dismiss
} else {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
}

Edit:

Apparently using presentationMode to dismiss a view in a Navigation stack still works on iOS 15 Beta 4. Unless there is a TabView inside the NavigationView:

struct ContentView: View {
    var body: some View {
        NavigationView {
            TabView {
                NavigationLink(destination: ChildView()) {
                    Text("View Child")
                }
            }
        }
    }
}

struct ChildView: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        Button(action: {
            print("Popping...")
            self.presentationMode.wrappedValue.dismiss()
        }, label: {
            Text("POP")
                .font(.headline)
        })
    }
}

presentatinMode doesn't work in this case.

Upvotes: 16

Views: 5685

Answers (4)

morteza
morteza

Reputation: 33

You can use this:

@Environment(\.dismiss) var dismiss

and here is the source.

Upvotes: -2

Levan Apakidze
Levan Apakidze

Reputation: 431

Since I don't like "just use deprecated stuff cause it works" approach I was looking for an answer on this matter as well.

The best solution I could come up so far which doesn't require too much code change as well and uses DismissAction when available is this:

extension EnvironmentValues {
    // this can be used as: @Environment(\.dismissable) var myDismiss
    // in any swiftui view and it will not complain about ios versions 
    var dismissable: () -> Void {
        return dismissAction
    }
    

    // this function abstracts the availability check so you can
    // avoid the conflicting return types and any other headache
    private func dismissAction() {
        if #available(iOS 15, *) {
            dismiss()
        } else {
            presentationMode.wrappedValue.dismiss()
        }
    }
}

just in case anyone's unclear with the usage of this new environment value here is an example (some parts, like navigation or presenting are ommited):

// view inside navigation/sheet
struct SomeInnerView: View {
    @Environment(\.dismissable) var myDismiss
    
    var body: some View {
        VStack {
            Button("dismiss me way 1") { myDismiss() } // tapping will result in a dismissing of this view
            Button("dismiss me way 2", action: myDismiss) // p.s this was not directly possible with DismissAction
        }
    }
}

Upvotes: 3

shbli
shbli

Reputation: 571

To support dismiss and iOS 14 add this extension to your EnvironmentValues

@available(iOS 14.0, *)
extension EnvironmentValues {
    var dismiss: () -> Void {
        { presentationMode.wrappedValue.dismiss() }
    }
}

Upvotes: 10

Ben10
Ben10

Reputation: 3259

I know this apple not yet fix the bug in SwiftUI. So far I am following the below approach it will work for me all the versions. Might be it will helpful for your situation

//presentationMode.wrappedValue.dismiss()
   
    if var topController = UIApplication.shared.windows.first!.rootViewController {
        while let presentedViewController = topController.presentedViewController {
            topController = presentedViewController
        }
        topController.dismiss(animated: true)
    }

Upvotes: 3

Related Questions