SwiftUI dismiss modal

SwiftModal DialogSwiftui

Swift Problem Overview


Since SwiftUI is declarative there is no dismiss method. How can is add a dismiss/close button to the DetailView?

struct DetailView: View {
  var body: some View {
  Text("Detail")
  }
}

struct ContentView : View {
  var body: some View {
  PresentationButton(Text("Click to show"), destination: DetailView())
  }
}

Swift Solutions


Solution 1 - Swift

struct ContentView: View {
    @State private var showModal = false
    
    var body: some View {
       Button("Show Modal") {
          self.showModal.toggle()
       }.sheet(isPresented: $showModal) {
            ModalView(showModal: self.$showModal)
        }
    }
}

struct ModalView: View {
    @Binding var showModal: Bool
    
    var body: some View {
        Text("Modal view")
        Button("Dismiss") {
            self.showModal.toggle()
        }
    }
}

Using presentationMode

You can use presentationMode environment variable in your modal view and calling self.presentaionMode.wrappedValue.dismiss() to dismiss the modal:

struct ContentView: View {

  @State private var showModal = false

  // If you are getting the "can only present once" issue, add this here.
  // Fixes the problem, but not sure why; feel free to edit/explain below.
  @SwiftUI.Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>


  var body: some View {
    Button(action: {
        self.showModal = true
    }) {
        Text("Show modal")
    }.sheet(isPresented: self.$showModal) {
        ModalView()
    }
  }
}


struct ModalView: View {

  @Environment(\.presentationMode) private var presentationMode

  var body: some View {
    Group {
      Text("Modal view")
      Button(action: {
         self.presentationMode.wrappedValue.dismiss()
      }) {
        Text("Dismiss")
      }
    }
  }
}

enter image description here

Solution 2 - Swift

In Xcode Beta 5, another way to do this is to use @State in the view that launches the modal, and add a binding in the modal view to control visibility of the modal. This doesn't require you to reach into the @Environment presentationMode variable.

struct MyView : View {
    @State var modalIsPresented = false

    var body: some View {
        Button(action: {self.modalIsPresented = true})  {
            Text("Launch modal view")
        }
        .sheet(isPresented: $modalIsPresented, content: {
            MyModalView(isPresented: self.$modalIsPresented)
        })
    }
}


struct MyModalView : View {
    @Binding var isPresented: Bool
    
    var body: some View {
        Button(action: {self.isPresented = false})  {
            Text("Close modal view")
        }
    }
}

Solution 3 - Swift

Here's a way to dismiss the presented view.

struct DetailView: View {
    @Binding
    var dismissFlag: Bool

    var body: some View {
        Group {
            Text("Detail")
            Button(action: {
                self.dismissFlag.toggle()
            }) {
                Text("Dismiss")
            }
        }

    }
}

struct ContentView : View {
    @State var dismissFlag = false

    var body: some View {
        Button(action: {
            self.dismissFlag.toggle()
        })
        { Text("Show") }
            .presentation(!dismissFlag ? nil :
                Modal(DetailView(dismissFlag: $dismissFlag)) {
                print("dismissed")
            })
    }
}

enter image description here

Solution 4 - Swift

iOS 15

Instead of presentationMode we can now use DismissAction.

Here is an example from the documentation:

struct SheetView: View {
    @Environment(\.dismiss) var dismiss

    var body: some View {
        NavigationView {
            SheetContents()
                .toolbar {
                    Button("Done") {
                        dismiss()
                    }
                }
        }
    }
}

Solution 5 - Swift

Seems that for Xcode 11 Beta 7 (this is on build 11M392r of Xcode) it's slightly different.

@Environment(\.presentationMode) var presentation


Button(action: { self.presentation.wrappedValue.dismiss() }) { Text("Dismiss") }

Solution 6 - Swift

You can implement this.

struct view: View {
    @Environment(\.isPresented) private var isPresented

    private func dismiss() {
        isPresented?.value = false
    }
}

Solution 7 - Swift

There is now a pretty clean way to do this in Beta 5.

import SwiftUI

struct ModalView : View {
    // In Xcode 11 beta 5, 'isPresented' is deprecated use 'presentationMode' instead
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var body: some View {
        Group {
            Text("Modal view")
            Button(action: { self.presentationMode.wrappedValue.dismiss() }) { Text("Dismiss") }
        }
    }
}

struct ContentView : View {
    @State var showModal: Bool = false
    var body: some View {
        Group {
            Button(action: { self.showModal = true }) { Text("Show modal via .sheet modifier") }
                .sheet(isPresented: $showModal, onDismiss: { print("In DetailView onDismiss.") }) { ModalView() }
        }
    }
}

Solution 8 - Swift

New in Swift 5.5 and SwiftUI 3:

@Environment(\.dismiss) var dismiss

Then in function or somewhere in body code, simply call:

self.dismiss()

Solution 9 - Swift

Since PresentationButton is easy to use but hiding the state wich is undermining the predictive character of SwiftUI I have implemented it with an accessible Binding.

public struct BindedPresentationButton<Label, Destination>: View where Label: View, Destination: View {
    /// The state of the modal presentation, either `visibile` or `off`.
    private var showModal: Binding<Bool>

    /// A `View` to use as the label of the button.
    public var label: Label

    /// A `View` to present.
    public var destination: Destination

    /// A closure to be invoked when the button is tapped.
    public var onTrigger: (() -> Void)?

    public init(
        showModal: Binding<Bool>,
        label: Label,
        destination: Destination,
        onTrigger: (() -> Void)? = nil
    ) {
        self.showModal = showModal
        self.label = label
        self.destination = destination
        self.onTrigger = onTrigger
    }

    public var body: some View {
        Button(action: toggleModal) {
            label
        }
        .presentation(
            !showModal.value ? nil :
                Modal(
                    destination, onDismiss: {
                        self.toggleModal()
                    }
                )
        )
    }

    private func toggleModal() {
        showModal.value.toggle()
        onTrigger?()
    }
}

This is how it is used:

struct DetailView: View {
    @Binding var showModal: Bool

    var body: some View {
        Group {
            Text("Detail")
            Button(action: {
                self.showModal = false
            }) {
                Text("Dismiss")
            }
        }
    }
}

struct ContentView: View {
    @State var showModal = false

    var body: some View {
        BindedPresentationButton(
            showModal: $showModal,
            label: Text("Show"),
            destination: DetailView(showModal: $showModal)
        ) {
            print("dismissed")
        }
    }
}

Solution 10 - Swift

Automatically pop if in Navigation or dismiss if Modal


Just take the presentationMode from the environment in the destination view and dismiss the wrappedValue from it:

struct DestinationView: View {
    @Environment(\.presentationMode) private var presentationMode

    var body: some View {
        Button("Dismiss") {
            self.presentationMode.wrappedValue.dismiss()
        }
    }
}


Demo ( pop / dismiss )

Pop Dismiss

Solution 11 - Swift

In Xcode 11.0 beta 7, the value is now wrapped, the following function is working for me:

func dismiss() {
    self.presentationMode.wrappedValue.dismiss()
}

Solution 12 - Swift

The modal views in SwiftUI seem to be simple until you start using them in a List or Form views. I have created a small library which wraps all the edge cases and makes the using of modal views the same as NavigationView-NavigationLink pair.

The library is open-sourced here: https://github.com/diniska/modal-view. You can include it into the project using Swift Package Manager, or just by copying the single file that the library includes.

The solution for your code would be:

struct DetailView: View {
    var dismiss: () -> ()
    var body: some View {
        Text("Detail")
        Button(action: dismiss) {
            Text("Click to dismiss")
        }
    }
}

struct ContentView : View {
    var body: some View {
        ModalPresenter {
            ModalLink(destination: DetailView.init(dismiss:)) {
                Text("Click to show")
            }
        }
    }
}

Additionally, there is an article with full description and examples: How to present modal view in SwiftUI

Solution 13 - Swift

Use Environment variable at PresentationMode. This GitHub link will maybe help you to solve the problem https://github.com/MannaICT13/Sheet-in-SwiftUI

This is simple solution:

struct ContentView2 : View {

    @Environment (\.presentationMode) var presentationMode

    var body : some View {
        VStack {
            Text("This is ContentView2")
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }, label: {
                Text("Back")    
            })    
        }
    }
}


struct ContentView: View {

    @State var isShowingSheet : Bool = false

    var body: some View {
        Button(action: {
            self.isShowingSheet.toggle()
        }, label: {
            Text("Click Here")
        }).sheet(isPresented: $isShowingSheet, content: {  
            ContentView2()
        })
    }
}

Solution 14 - Swift

One way to do this might be to declare you own modifier for modal presentation and dismissal.

extension View {
  
  func showModal<T>(_ binding: Binding<Bool>, _ view: @escaping () -> T) -> some View where T: View {
    
    let windowHeightOffset = (UIApplication.shared.windows.first?.frame.height ?? 600) * -1
    
    return ZStack {
      
      self
      
      view().frame(maxWidth: .infinity, maxHeight: .infinity).edgesIgnoringSafeArea(.all).offset(x: 0, y: binding.wrappedValue ? 0 : windowHeightOffset)
      
    }
    
  }
}

Then you can use the modifier on any view that you wish to tell how to display a view and dismiss that view. Just like a popover or sheet modifier.

struct ContentView: View {
  
  @State var showModal = false
    
  var body: some View {
    
    Text("Show").foregroundColor(.blue).onTapGesture {
      withAnimation(.easeIn(duration: 0.75)) {
        self.showModal = true
      }
    }.showModal($showModal, {
      
      Text("Dismiss").foregroundColor(.blue).onTapGesture {
        withAnimation(.easeIn(duration: 0.75)) {
          self.showModal = false
        }
      }
      
    })
    
    
  }
}    

The presentation is full screen from the top, if you wish it to come from the side, change the transition inside the modifier to leading or trailing. Other transitions would work too, like opacity or scale.

enter image description here

Solution 15 - Swift

SwiftUI 2 code sample (works with mobiles also)

(sample code doesnt work with swift 1, but you still can try it without @main block)

Full app sample for using sheets:

@main
struct TestAppApp: App {
    var body: some Scene {
        WindowGroup {
            SheetLink(text: "click me!", content: ChildView() )
                .padding(.all, 100)
        }
    }
}

struct ChildView: View {
    var body: some View {
        Text("this is subView!")
    }
}

enter image description here

and when subview is larger than main view:

enter image description here

And code behind this:

struct SheetLink<Content> : View where Content: View {
    @State var text: String
    @State var displaySheet = false
    @State var content: Content


    var body: some View {
        HStack {
            Button(text, action: { self.displaySheet = true } ).buttonStyle(PlainButtonStyle()).foregroundColor(.blue)
        }
        .sheet(isPresented: $displaySheet) {
            SheetTemplateView(isPresented: self.$displaySheet, content: content)
        }
    }
}

struct SheetTemplateView<Content> : View where Content: View {
    @Binding var isPresented: Bool
    @State var content: Content
    
    var body: some View {
        VStack{
            HStack{
                Button("Back!", action: { isPresented.toggle() } ).buttonStyle(PlainButtonStyle()).foregroundColor(.blue)
                Spacer()
            }
            Spacer()
            content
            Spacer()
        }
        .padding()
    }
}

Solution 16 - Swift

You can use Presentation mode to dismiss. Declare

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

And then when required, dismiss it by

self.presentationMode.wrappedValue.dismiss()

Solution 17 - Swift

You can use SheetKit to dismiss all sheets

SheetKit().dismissAllSheets()

or present new UISheetPresentationController

sheetKit.present(with: .bottomSheet){
  Text("Hello world")
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionUgo AranginoView Question on Stackoverflow
Solution 1 - SwiftM RezaView Answer on Stackoverflow
Solution 2 - SwiftthieznView Answer on Stackoverflow
Solution 3 - SwiftSadaView Answer on Stackoverflow
Solution 4 - Swiftpawello2222View Answer on Stackoverflow
Solution 5 - SwiftTomm PView Answer on Stackoverflow
Solution 6 - SwiftiOSCSView Answer on Stackoverflow
Solution 7 - SwiftChuck HView Answer on Stackoverflow
Solution 8 - SwiftTom GODDARDView Answer on Stackoverflow
Solution 9 - SwiftUgo AranginoView Answer on Stackoverflow
Solution 10 - SwiftMojtaba HosseiniView Answer on Stackoverflow
Solution 11 - SwiftGareth JonesView Answer on Stackoverflow
Solution 12 - SwiftDenisView Answer on Stackoverflow
Solution 13 - SwiftMannaICT13View Answer on Stackoverflow
Solution 14 - SwiftjnblanchardView Answer on Stackoverflow
Solution 15 - SwiftAndrew____Pls_Support_UkraineView Answer on Stackoverflow
Solution 16 - SwiftSoumiMaitiView Answer on Stackoverflow
Solution 17 - SwiftBob XuView Answer on Stackoverflow