Son Nguyen
Son Nguyen

Reputation: 1644

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.

Upvotes: 45

Views: 29265

Answers (7)

swity learner
swity learner

Reputation: 454

Use presentationBackground to set desired background for modals (fullScreenCover, sheet, popover). From the documentation:

Allows views behind the presentation to show through translucent styles.

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{
                    VStack {
                        ProgressView()
                        Button("Stop loading") { isLoading.toggle() }
                    }
                }
                .presentationBackground(.black.opacity(0.5))
            }
        }
    }
}

Availability

@available(iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4, *)

Upvotes: 39

Quanhua Guan
Quanhua Guan

Reputation: 463

struct TransparentBackgroundView: UIViewRepresentable {
    var color: UIColor = .clear
    func makeUIView(context: Context) -> UIView {
        let view = _UIBackgroundBlurView()
        view.color = color
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

class _UIBackgroundBlurView: UIView {
    var color :UIColor = .clear
    override func layoutSubviews() {
        super.layoutSubviews()
        superview?.superview?.backgroundColor = color
    }
}

Usage:

PresenterView().fullScreenCover(isPresented: $isPresented) {
    PresentedView().background(TransparentBackgroundView())
}

Upvotes: 1

f3dm76
f3dm76

Reputation: 804

Update: please use Povilas's answer above to avoid flickering screen issue

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) {}
}

Upvotes: 10

Ahmed M. Hassan
Ahmed M. Hassan

Reputation: 1286

I found this cleaner solution for the flicker issue in the clear background.

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

Usage

PresenterView()
    .fullScreenCover(isPresented: $isPresented) {
        PresentedView()
            .background(ClearBackgroundView())
    }

Upvotes: 23

Asperi
Asperi

Reputation: 258375

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) {}
}

Upvotes: 55

Povilas
Povilas

Reputation: 565

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) {}
    
}

Upvotes: 23

TevTra
TevTra

Reputation: 7

You can also use the Material background type:

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

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

Upvotes: -2

Related Questions