I don't think that is the right way for SwiftUI to present any kind of view.
The paradigm works by creating specific views that show some content on the screen, so you can have more than one view inside the body of superview that needs to present something. So the SwiftUI 2, on iOS 14, will not accept that and the developer should call all presentations in the superview that can be accepted in some cases, but will have moments that will be better if the specific views present the content.
I implemented a solution for that and test on Swift 5.3 with Xcode 12.1 on iOS 14.1
struct Presentation<Content>: View where Content: View {
enum Style {
case sheet
case popover
case fullScreenCover
}
@State private var isTrulyPresented: Bool = false
@State private var willPresent: Bool = false
@Binding private var isPresented: Bool
let content: () -> Content
let dismissHandler: (() -> Void)?
let style: Style
init(_ style: Style, _ isPresented: Binding<Bool>, onDismiss: (() -> Void)?, content: @escaping () -> Content) {
self._isPresented = isPresented
self.content = content
self.dismissHandler = onDismiss
self.style = style
}
@ViewBuilder
var body: some View {
if !isPresented && !willPresent {
EmptyView()
} else {
switch style {
case .sheet:
EmptyView()
.sheet(isPresented: $isTrulyPresented, onDismiss: dismissHandler, content: dynamicContent)
case .popover:
EmptyView()
.popover(isPresented: $isTrulyPresented, content: dynamicContent)
case .fullScreenCover:
EmptyView()
.fullScreenCover(isPresented: $isTrulyPresented, onDismiss: dismissHandler, content: dynamicContent)
}
}
}
}
extension Presentation {
var dynamicContent: () -> Content {
if isPresented && !isTrulyPresented {
OperationQueue.main.addOperation {
willPresent = true
OperationQueue.main.addOperation {
isTrulyPresented = true
}
}
} else if isTrulyPresented && !isPresented {
OperationQueue.main.addOperation {
isTrulyPresented = false
OperationQueue.main.addOperation {
willPresent = false
}
}
}
return content
}
}
After that, I can implement these methods for all views in SwiftUI
public extension View {
func _sheet<Content>(
isPresented: Binding<Bool>,
content: @escaping () -> Content
) -> some View where Content: View {
self.background(
Presentation(
.sheet,
isPresented,
onDismiss: nil,
content: content
)
)
}
func _sheet<Content>(
isPresented: Binding<Bool>,
onDismiss: @escaping () -> Void,
content: @escaping () -> Content
) -> some View where Content: View {
self.background(
Presentation(
.sheet,
isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}
public extension View {
func _popover<Content>(
isPresented: Binding<Bool>,
content: @escaping () -> Content
) -> some View where Content: View {
self.background(
Presentation(
.popover,
isPresented,
onDismiss: nil,
content: content
)
)
}
}
public extension View {
func _fullScreenCover<Content>(
isPresented: Binding<Bool>,
content: @escaping () -> Content
) -> some View where Content: View {
self.background(
Presentation(
.fullScreenCover,
isPresented,
onDismiss: nil,
content: content
)
)
}
func _fullScreenCover<Content>(
isPresented: Binding<Bool>,
onDismiss: @escaping () -> Void,
content: @escaping () -> Content
) -> some View where Content: View {
self.background(
Presentation(
.fullScreenCover,
isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}
ActiveSheet
enum and switch does work well as a solution, if you're looking to still have two sheet views as I am. – Betthezul