Reputation: 11275
I know this question has been asked and answered before. Not sure if this changed/broke in Beta 4 for SwiftUI, but I can't seem to get the isPresented
solution to work to dismiss a modal shown with sheet
.
Here is a simple example of what I tried, I thought this would work, but clicking "Close" does nothing and when I inspect self.isPresented?.value
it's nil
.
struct DetailView: View {
@Environment(\.isPresented) var isPresented: Binding<Bool>?
var body: some View {
Button(action: {
self.isPresented?.value = false
}) {
Text("Close")
}
}
}
struct ContentView: View {
@State private var showingModal = false
var body: some View {
Button(action: {
self.showingModal = true
}) {
Text("Show detail")
}.sheet(isPresented: $showingModal) {
DetailView()
}
}
}
Update based on suggestion, this works. Seems like too much book keeping to me, hope this gets updated.
struct DetailView: View {
@Binding var showingModal: Bool
var body: some View {
Button(action: {
self.showingModal = false
}) {
Text("Close")
}
}
}
struct ContentView: View {
@State private var showingModal = false
var body: some View {
Button(action: {
self.showingModal = true
}) {
Text("Show detail")
}.sheet(isPresented: $showingModal) {
DetailView(showingModal: self.$showingModal)
}
}
}
Upvotes: 2
Views: 5426
Reputation: 10116
Another solution is to add a delegate
property to your SwifUI view which passes the dismiss action back to the presenter.
protocol MySwiftUIViewDelegate: class {
func myDismissAction()
}
struct MySwiftUIView {
weak var delegate: MySwiftUIViewDelegate?
var body: some View {
Button("Dismiss") {
self.delegate?.myDismissAction()
}
}
}
class MyViewController: UIViewController, MySwiftUIViewDelegate {
func presentMyView() {
var myView = MySwiftUIView()
myView.delegate = self
let hostingViewController = UIHostingController(rootView: myView)
present(vc, animated: true, completion: nil)
}
// MARK: - MySwiftUIViewDelegate
func myDismissAction() {
dismiss(animated: true)
}
}
While this may seem a bit convoluted, it is also arguably better to make the presenter responsible for handling the dismissal, so that the view doesn't have to know how it was presented (e.g. push vs modal), thus making your code more modular. Plus you might want other delegated methods, depending on what you're working on, so you might have a delegate protocol already. And it gives you a convenient place to execute any additional code when the view is dismissed.
(Although keep in mind that depending on the modal presentation style / settings, users may also be able to dismiss by pulling the view down.)
Upvotes: 3
Reputation: 842
A somewhat neater solution could be to define a callback function:
struct DetailView: View {
var dismiss: () -> ()
var body: some View {
Button(action: dismiss) {
Text("Close")
}
}
}
struct ContentView: View {
@State private var showingModal = false
var body: some View {
Button(action: {
self.showingModal = true
}) {
Text("Show detail")
}.sheet(isPresented: $showingModal) {
DetailView(dismiss: { self.showingModal = false })
}
}
}
The benefit over the extra bookkeeping is that DetailView
no longer needs to be aware that it is a modal, allowing it to be used in different contexts. Furthermore, you keep all the relevant code for showing and dismissing the modal inside the original view.
Now, regardless of which method you use to dismiss the modal, you should be wary that modals are still very buggy, even in beta 6. I lost too much time on various situations where none of these solutions work as they should:
Button
in ContentView
inside a List
(or ScrollView
, as List
is just a special type of ScrollView
) then the button only works once. You can show the modal and dismiss it, but you won't be able to show it again ...NavigationView
and you add the Button
to the .navigationBarItems
, then you will be able to show the modal as often as you like. However, the Close button in DetailView
will not work ...So far I have not been able to make a dismiss button work correctly in these situations. This limits the use of a modal quite a bit in real-life apps. There is every chance that this will be fixed for the GM, but just be aware of these issues until they are known to be fixed.
Upvotes: 2
Reputation: 8193
Beta 6
Use presentationMode
from the @Environment
.
struct SomeView: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Ohay!")
Button("Close") {
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
Upvotes: 11
Reputation: 535139
In the presenting view, configure a State bool set to false, and pass the binding into the sheet
call. To present, set it to true
. But also pass the binding into the second view, so that a button there can set it to false
again.
struct ContentView : View {
@State var showSheet = false
var body: some View {
Button("Show Sheet") {
self.showSheet.toggle()
}.sheet(isPresented: self.$showSheet) {
Modal(isPresented:self.$showSheet)
}
}
}
struct Modal : View {
@Binding var isPresented : Bool
var body: some View {
Button("Done", action: {self.isPresented = false})
}
}
Upvotes: 2