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