The visualEffect view modifier applies animatable visual effects that are computed from the
view's own layout. Its closure hands you a GeometryProxy, so an effect can depend on the frame
without a GeometryReader wrapper. Introduced at WWDC 23, iOS 17+.
The Effect Closure
The closure receives the initial effect state (an EmptyVisualEffect) and a GeometryProxy with
the frame, size, and safe area. Chain effects onto the initial value and return the result.
Text("Hello World!")
.visualEffect { initial, geometry in
initial.offset(geometry.size)
}
What Counts as a Visual Effect
A visual effect changes how the view looks without changing its layout. scaleEffect, offset,
blur, contrast, saturation, opacity, and rotationEffect all conform to the
VisualEffect protocol and chain inside the closure.
Text("Hello World!")
.visualEffect { initial, geometry in
initial
.blur(radius: 8)
.opacity(0.9)
.scaleEffect(.init(width: 2, height: 2))
}
frame and padding are not visual effects. They change layout, so they cannot appear inside the
closure.
Animating It
The effects are animatable. Drive them from state and attach animation as usual.
Text("Hello World!")
.visualEffect { initial, geometry in
initial.scaleEffect(
CGSize(width: isScaled ? 2 : 1, height: isScaled ? 2 : 1)
)
}
.animation(.smooth, value: isScaled)
When to Use It
Reach for visualEffect only when the effect depends on layout information. When it does not, the
plain modifiers (opacity, offset, scaleEffect) do the same job and run on older OS versions.