Replicate background blur used for SwiftUI GraphicalDatePickerStyle for a custom control

Viewed 342

I'm trying to develop a custom control for use in a form that behaves in a similar manner to SwiftUI's DatePicker (within a Form it defaults to GraphicalDatePickerStyle).

Apple's control displays the calendar and time picker modally with the background blurred out. I can manage the modal presentation using the .fullScreenCover modifier. Background blur is also possible by adding a UIVisualEffectView to the background of the displayed control. However, the blur applied in this way is far too heavy-handed even using the lightest options provided by the system defaults. I'm also unhappy about the reliance on modifying superview backgrounds via DispatchQueue.main.async - it works but makes me feel uncomfortable.

Is there any way of getting closer to Apple's implementation? My attempts to add a vibrancy effect layer didn't get me anywhere and you're not supposed to touch the alpha of UIEffectViews according to documentation...

Code and comparison of the visual effects (desired and current iteration) below:

import SwiftUI

struct ContentView: View {
    @State private var isPresented = false
    @State private var interval: TimeInterval = 0
    
    var body: some View {
        Form {
            Text("Row")
            Text("Row")
            Text("Row")
            Text("Row")
            
            DatePicker("Date", selection: .constant(Date()))
            
            Button("Present custom control") {
                self.isPresented.toggle()
            }
            .fullScreenCover(isPresented: $isPresented) {
                ZStack {
                    Color.white.opacity(0.1)
                        .edgesIgnoringSafeArea(.all)
                    // below is a placeholder for the custom control
                    Button("Dismiss") { isPresented.toggle() }
                        .frame(width: 200, height: 200)
                        .background(Color.white)
                        .cornerRadius(16)
                }
                .background(BackgroundBlurView())
            }
            
            Text("Other stuff here")
                .background(Color.red)
        }
    }
}

struct BackgroundBlurView: UIViewRepresentable {
    func makeUIView(context: Context) -> UIView {
        let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial))
        // this works but it looks a bit hacky... code smell?
        DispatchQueue.main.async {
            view.superview?.superview?.backgroundColor = .clear
        }
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // nothing needed here
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
        }
    }
}

Form (before control tapped) Form (before control tapped)

Background blur applied by DatePicker Background blur applied by DatePicker

My attempt - not remotely close! Current attempt - significant blur and loss of vibrancy

1 Answers

Firstly, it seems like Apple's blur in the example you provided also had a .brightness modifier, to make the background darker. (You can implement this by adding .brightness(- (some number)).

Secondly, you can use the .blur() modifier, where you can pass in a custom blur amount. Another solution is to use Apple's super handy Material. There are a few different materials, and you should pass them into your ZStack instead of using Color.white.opacity(0.1), which would look something like this:

ZStack {
    Material.ultraThin

    //rest of view
}
Related