swiftuipositionoverlayframeblur

"background blur" in SwiftUI?


I want to draw image overlapped with a blurred panel with a text. The problem is the panel frame depends on text frame. In the same time mask modifier is set separately from text and panel.

My code:

struct SomeView: View {
    @State var frame: CGRect = .zero
    
    var body: some View {
        ZStack {
            Image(.defaultCard)
            Image(.defaultCard)
                .blur(radius: 30)
                .mask {
                    Rectangle()
                        .frame(width: frame.width, height: frame.height)
                        .position(x: frame.midX, y: frame.midY)
                }
            ZStack(alignment: .bottomLeading) {
                Color.clear
                HStack {
                    Text("...")
                    Text("...")
                }
                .padding()
                .background {
                    GeometryReader { geometry in
                        Color.clear
                            .onAppear {
                                frame = geometry.frame(in: .global)
                            }
                            .onChange(of: geometry.size) { _ in
                                frame = geometry.frame(in: .global)
                            }
                    }
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

Everything is drawn but the mask position is incorrect. The following code returns incorrect frame:

frame = geometry.frame(in: .global)

How to resolve this issue? I understand that I could use blur from UIKit but SwiftUI blur is more similar to my requirements.


Solution

  • One way to position the mask is to use .matchedGeometryEffect. This way, you don't need to measure the size and position of the panel at all.

    A Namespace is needed instead of a state variable and an arbitrary identifier is needed to match the mask to the panel. The updated example below uses "Panel" as identifier.

    struct SomeView: View {
        @Namespace private var ns
    
        var body: some View {
            ZStack(alignment: .bottomLeading) {
                Image(.defaultCard)
                    .resizable()
                    .scaledToFit()
                Image(.defaultCard)
                    .resizable()
                    .scaledToFit()
                    .blur(radius: 30)
                    .mask {
                        Rectangle()
                            .matchedGeometryEffect(id: "Panel", in: ns, isSource: false)
                    }
                VStack {
                    Text("The quick brown fox")
                    Text("jumps over the lazy dog")
                }
                .padding()
                .matchedGeometryEffect(id: "Panel", in: ns)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
    

    Screenshot