SwiftUI: Translucent background for fullScreenCover

Posted on

SwiftUI: Translucent background for fullScreenCover

So technically I want to show a loading screen view. I’m using fullScreenCover.

struct ContentView: View {
    
    @State private var isLoading = false
        
    var body: some View {
        VStack {
            Text("Hello there")
            Button("Start loading") {
                isLoading.toggle()
            }
            .fullScreenCover(isPresented: $isLoading) {
                ZStack{
                    Color.black.opacity(0.5).edgesIgnoringSafeArea(.all)
                    VStack {
                        ProgressView()
                        Button("Stop loading") {
                            isLoading.toggle()
                        }
                    }
                }
            }
        }
    }
}

The problem is that I cannot make this loading screen translucent. sheet or popover behave the same way.

Solution :

Here is a demo of possible way. Parameters of visual effect you can tune for your needs.

Tested with Xcode 12 / iOS 14.

       // ... other code
            .fullScreenCover(isPresented: $isLoading) {
                ZStack{
                    Color.black.opacity(0.5).edgesIgnoringSafeArea(.all)
                    VStack {
                        ProgressView()
                        Button("Stop loading") {
                            isLoading.toggle()
                        }
                    }
                }
                .background(BackgroundBlurView())
            }
        }
    }
}

struct BackgroundBlurView: UIViewRepresentable {
    func makeUIView(context: Context) -> UIView {
        let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
        DispatchQueue.main.async {
            view.superview?.superview?.backgroundColor = .clear
        }
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {}
}

Building on top of f3dm76 answer:

I changed it so content behind would not flicker (that happened to me with lazy images loading behind fullScreenCover). Also I wanted to use custom transitions for full screen content (or in some cases no animation at all), so I removed default animations with this approach as well.

extension View {

    func transparentNonAnimatingFullScreenCover<Content: View>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View {
        modifier(TransparentNonAnimatableFullScreenModifier(isPresented: isPresented, fullScreenContent: content))
    }
    
}

private struct TransparentNonAnimatableFullScreenModifier<FullScreenContent: View>: ViewModifier {
    
    @Binding var isPresented: Bool
    let fullScreenContent: () -> (FullScreenContent)
    
    func body(content: Content) -> some View {
        content
            .onChange(of: isPresented) { isPresented in
                UIView.setAnimationsEnabled(false)
            }
            .fullScreenCover(isPresented: $isPresented,
                             content: {
                ZStack {
                    fullScreenContent()
                }
                .background(FullScreenCoverBackgroundRemovalView())
                .onAppear {
                    if !UIView.areAnimationsEnabled {
                        UIView.setAnimationsEnabled(true)
                    }
                }
                .onDisappear {
                    if !UIView.areAnimationsEnabled {
                        UIView.setAnimationsEnabled(true)
                    }
                }
            })
    }
    
}

private struct FullScreenCoverBackgroundRemovalView: UIViewRepresentable {
    
    private class BackgroundRemovalView: UIView {
        
        override func didMoveToWindow() {
            super.didMoveToWindow()
            
            superview?.superview?.backgroundColor = .clear
        }
        
    }
    
    func makeUIView(context: Context) -> UIView {
        return BackgroundRemovalView()
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
    
}

Asperi’s answer is beautiful, but in case you want background to be transparent and not blurred, here is how you can modify it. I also moved the code into a modifier for convenience. (xcode 13.3, iOS 15.4.1)

extension View {

    func transparentFullScreenCover<Content: View>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View {
        fullScreenCover(isPresented: isPresented) {
            ZStack {
                content()
            }
            .background(TransparentBackground())
        }
    }
}

struct TransparentBackground: UIViewRepresentable {

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        DispatchQueue.main.async {
            view.superview?.superview?.backgroundColor = .clear
        }
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {}
}

You can also use the Material background type:

ZStack{
   ...
}
.background(.ultraThinMaterial)

See documentation for more usage:
https://developer.apple.com/documentation/swiftui/material

Leave a Reply

Your email address will not be published. Required fields are marked *