I am working on creating a custom Popup View based on a .fullscreenCover. The .fullscreenCover is used to place the Popup content on screen on a semi-transparent background.
While this works on iOS 18, there is a problem on iOS 17: When the Popup content contains a .sheet, the background is not transparent any more but opaque.
Image: iOS 17. When showing the Popup an opaque background covers the main content. When tapping on the background it turns transparent.
Image: iOS 18. Everything works as intended. When showing the Popup the main background is covered with a semi-transparent background.
Removing the .sheet(...) from the Popup content solves the problem. It does not matter if the sheet is used or not. Adding it to the view code is enough to trigger the problem.
Using a .sheet within a .fullscreenCover should not be a problem as far as I know.
Is this a bug in iOS 17 or is there something wrong with my code?
Code:
struct SwiftUIView: View {
@State var isPresented: Bool = false
@State var sheetPresented: Bool = false
var body: some View {
ZStack {
VStack {
Color.red.frame(maxHeight: .infinity)
Color.green.frame(maxHeight: .infinity)
Color.yellow.frame(maxHeight: .infinity)
Color.blue.frame(maxHeight: .infinity)
}
Button("Show") {
isPresented = true
}
.padding()
.background(.white)
Popup(isPresented: $isPresented) {
VStack {
Button("Dismiss") {
isPresented = false
}
}
.frame(maxWidth: 300)
.padding()
.background(
RoundedRectangle(cornerRadius: 20)
.fill(.white)
)
.sheet(isPresented: $sheetPresented) {
Text("Hallo")
}
}
}
}
}
struct Popup<Content: View>: View {
@Binding var isPresented: Bool
let content: () -> Content
init(isPresented: Binding<Bool>, @ViewBuilder _ content: @escaping () -> Content) {
_isPresented = isPresented
self.content = content
}
@State private var internalIsPresented: Bool = false
@State private var isShowing: Bool = false
let transitionDuration: TimeInterval = 0.5
var body: some View {
ZStack { }
.fullScreenCover(isPresented: $internalIsPresented) {
VStack {
content()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
Color.black.opacity(0.5)
.opacity(isShowing ? 1 : 0)
.animation(.easeOut(duration: transitionDuration), value: isShowing)
.ignoresSafeArea()
)
.presentationBackground(.clear)
.onAppear {
isShowing = true
}
.onDisappear {
isShowing = false
}
}
.onChange(of: isPresented) { _ in
withoutAnimation {
internalIsPresented = isPresented
}
}
}
}
extension View {
func withoutAnimation(action: @escaping () -> Void) {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
action()
}
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI