I wonder why .fullScreenCover(isPresented:)
requires a Binding
instead of a simple Bool. I understand that .sheet
requires a binding report back that it was closed, e.g. through a swipe gesture.
.fullScreenCover
on the other hand, does not offer any build in methods to close the cover, does it? So the neither the user nor iOS can close the cover. So there is nothing to report back and thus a simple Bool parameter would be sufficient like in this example:
struct CoverTestView: View {
@State var visible: Bool = false
var body: some View {
// Pass simple Bool instead of Binding
CoverView(visible: visible)
Button("Toggle") {
visible.toggle()
}
}
}
struct CoverView: View {
let visible: Bool
var body: some View {
if visible {
Text("Hello, World!")
}
}
}
What is the Binding needed for?
Of course the user needs some way to close the cover and thus a binding is required. But it would be required in the cover content, not in .fullScreenCover
itself.
struct CoverTestView: View {
@State var visible: Bool = false
var body: some View {
Button("Toggle") {
visible.toggle()
}
.fullScreenCover(isPresented: visible) {
CoverView(visible: $visible)
}
}
}
struct CoverView: View {
@Binding var visible: Bool
var body: some View {
if visible {
Text("Hello, World!")
}
Button("Dismiss") {
visible = false
}
}
}
I assume that the iOS engineers used the Binding for a reason. So what am I missing?
But it would be required in the cover content, not in
.fullScreenCover
itself.
Sure, SwiftUI could be designed that way, but it is not. In the presented view, you are allowed to use the dismiss
environment value to dismiss a fullscreen cover.
struct CoverView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Text("Hello, World!")
Button("Dismiss") {
dismiss()
}
}
}
Because of this, fullScreenCover
needs to be able to change the value of isPresented
too, and so it needs to be a Binding
.
This design also reduces the number of parameters you'd need to pass to your views. The views themselves can get the dismiss
environment value all on their own, without having an extra initialiser parameter.
Furthermore, if the view you are presenting is some UIViewController
that uses UIViewController.dismiss
to dismiss itself, isPresented
will still be set to false afterwards, hence maintaing the source of truth.
struct UIKitStuff: UIViewControllerRepresentable {
class SomeController: UIViewController {
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
// this also sets the binding back to false!
dismiss(animated: true)
}
}
func makeUIViewController(context: Context) -> some UIViewController {
SomeController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
If isPresented
is not a Binding
, then UIViewController.dismiss
will either need to do nothing (which reduces the reusability of existing UIViewController
s), or the SwiftUI state will no longer reflect reality (visible
being true despite there is no fullscreen cover). I'm sure you'd agree that neither is desirable.