swiftswiftui

SwiftUI .sheet causing white flickering of background


I'm having a strange issue where the edges of my background view are flickering white when the sheet is presented/dismissed. The issue only seems to be visible when the system is in Light Mode, Dark Mode is fine.

You can see it here - flickering video demo

Has anyone seen this and know how to fix it?

import SwiftUI

struct ContentView: View {
    
    @State private var isShowing: Bool = false
    
    var body: some View {
            ZStack {
                Color.black
                    .ignoresSafeArea()
                Button {
                    isShowing = true
                } label: {
                    Label("show", systemImage: "questionmark.circle")
                }
            }
            .sheet(isPresented: $isShowing) {
                EmptyView()
        }

    }
}

Solution

  • By inspecting the view hierarchy, we can see that SwiftUI presents your root view using a UIHostingController. The view of this hosting controller has its backgroundColor set to UIColor.systemBackground. Your Color.black is then overlayed on top of that.

    As you know, when a large sheet is presented, the view behind the sheet is designed to shrink and shift downwards a little bit. When you drag the sheet around, the view behind the sheet also moves around a bit. What is happening here seems to be the movement of Color.black isn't quite in sync with the UIView that is hosting it. Therefore, sometimes some of that .systemBackground color under the Color.black is revealed. .systemBackground is a white color in light mode, so you can clearly see it. It is a black color in dark mode, so it blends in with Color.black and you don't notice it.

    I don't think you can fix the movements being out of sync. You'll have to wait for Apple to do it. What you can do is set the background color to UIColor.black so it is not noticeable even in light mode.

    struct BackgroundFixer: UIViewControllerRepresentable {
        class VC: UIViewController {
            override func willMove(toParent parent: UIViewController?) {
                // "parent" will be the UIHostingController we want when this
                // is added as a background to the root ZStack
                parent?.view.backgroundColor = .black
            }
        }
        
        func makeUIViewController(context: Context) -> VC {
            VC()
        }
        
        func updateUIViewController(_ uiViewController: VC, context: Context) {
            
        }
    }
    
    // Add this to the ZStack
    
    ZStack {
        Color.black
            .ignoresSafeArea()
        Button {
            isShowing = true
        } label: {
            Label("show", systemImage: "questionmark.circle")
        }
    }
    .background {
        BackgroundFixer()
    }
    .sheet(isPresented: $isShowing) {
        EmptyView()
    }