28

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.

Son Nguyen
  • 1,124
  • 2
  • 10
  • 24

6 Answers6

49

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) {}
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I tried but it doesn't work. I tried from 1 superview all the way to 4 superview (the window, after that superview is nil). what is the difference between using a custom view and simply using a translucent color for the background? – Son Nguyen Oct 11 '20 at 06:46
  • I take back what I said, it does show a blurred background. Thanks – Son Nguyen Oct 11 '20 at 06:57
  • 3
    do you know how to turn off the animation as well? – Son Nguyen Oct 11 '20 at 07:05
  • I tried a variant of this where the `superview?.backgroundColor` is updated synchronously. This didn't work, the value seemed to get overwritten. Does anyone know why? Specifically do you know when the superview's background color would get set? – TMin Oct 18 '22 at 21:18
  • 2
    This is not working for me. Do you guys have any other solution of this? – Tulon Apr 08 '23 at 08:51
  • I ran into a race condition where this failed to work intermittently. Its possible for the async closure to execute before the view is added to the view hierarchy (at which point it doesn't ave a `superview.superview`. I added a recursive function that calls itself if `view.superview?.superview == nil` otherwise it sets these values. It seems to be working to cover this corner case. – TMin Jun 15 '23 at 21:43
  • Using this approach, I have 1 frame of white background randomly. Sometimes no flickering 10 times in a row, sometimes 1:1, sometimes several times in a row with flickering. – bodich Aug 28 '23 at 19:37
12

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) {}
    
}
Povilas
  • 445
  • 3
  • 17
  • 1
    Thanks for saving me ages on this flickering issue – Yosi199 Aug 25 '22 at 06:26
  • 1
    I would love to see an example of how you did a custom transition with this, I can't seem to get that working – Colin Tremblay Aug 31 '22 at 00:50
  • @ColinTremblay I have boolean variable in the view that needs to be presented full screen. It allows me to trigger animation / transition on that variable change. I change it in "onAppear" call. – Povilas Sep 06 '22 at 19:02
  • Oh my Dear Lord, thank you!! That flickering was a PAIN – f3dm76 Dec 27 '22 at 09:09
  • @Povilas could you show us an example? – Jalil Jul 21 '23 at 14:35
  • Works perfect with simply .background(FullScreenCoverBackgroundRemovalView()), thanks, saved me a lot of time! But much simpler to remove animation with this .transaction { transaction in transaction.disablesAnimations = false }, not editing globals. – bodich Aug 29 '23 at 13:15
8

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) {}
}
f3dm76
  • 680
  • 7
  • 21
  • It is causing default white background flickering. In 30-50% cases we have 1 frame of white background before background color will be set to.clear. – bodich Aug 28 '23 at 19:42
  • @bodich please see the answer from Povilas above this one – f3dm76 Aug 29 '23 at 08:48
  • I have already used mix of both approaches. Background UIView from Povilas but simple to use .background(TransparentBackground()). My comment here just warns other devs from using your approach to save a lot of time on debugging "why the heck it's flickering" for hours. – bodich Aug 29 '23 at 13:13
7

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())
    }
Ahmed M. Hassan
  • 709
  • 9
  • 14
3

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, *)
0

You can also use the Material background type:

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

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

TevTra
  • 11
  • 1