Geometry Group
Geometry group is a special container view that’s designed specifically to resolve certain animation inconsistencies that were previously challenging to manage or impossible to address effectively.
Official definition
According to Apple documentation, geometry group:
Isolates the geometry (e.g. position and size) of the view from its parent view.
By default SwiftUI views push position and size changes down through the view hierarchy, so that only views that draw something (known as leaf views) apply the current animation to their frame rectangle. However in some cases this coalescing behavior can give undesirable results; inserting a geometry group can correct that. A group acts as a barrier between the parent view and its subviews, forcing the position and size values to be resolved and animated by the parent, before being passed down to each subview.
Still confused?
If you are still confused like I was, let me simplify it for you: In SwiftUI, geometry animations exclusively affect views that are currently visible on the screen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct MyView: View {
@State private var condition: Bool = false
var body: some View {
ZStack(alignment: .topLeading) {
child1
child2
}
.geometryGroup()
.frame(
width: condition ? 200 : 100,
height: condition ? 200 : 100
)
.onTapGesture {
withAnimation {
condition.toggle()
}
}
}
@ViewBuilder private var child1: some View {
Color.red
}
@ViewBuilder private var child2: some View {
if condition {
Color.blue
.frame(width: 20, height: 20)
.clipShape(.circle)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
}
As you can see, the blue dot fails to animate correctly without the geometry group. After adding .geometryGroup()
, the blue dot animate it’s position alongside the red rectangle, which is the desired result.
How it works under the hood?
Let’s break down the timeline sequence.
Without geometryGroup
- (t=0)
condition
changed fromfalse
totrue
. - (t=1) SwiftUI internally creates a transaction with animation information for the current state change and propagates it down the view hierarchy.
- (t=2) The
frame
, which is invisible, of theZStack
is adjusted, changing the size from100x100
to200x200
. - (t=3)
ZStack
propose200x200
to bothchild1
andchild2
.child1
changes its size based on the proposed size change received from the parent view frame. Since the transaction includes animation information, this change has an animated effect. At the same time,child2
appears with default transition animation. Since the size proposed tochild2
is200x200
so SwiftUI just renders it in200x200
frame. - (t=N) The geometry animation for
child1
completes, and the transition animation forchild2
completes.
With geometryGroup
- (t=0)
condition
changed fromfalse
totrue
. - (t=1) SwiftUI internally creates a transaction with animation information for the current state change and propagates it down the view hierarchy.
- (t=2) The
frame
, which is invisible, of theZStack
is adjusted, changing the size from100x100
to200x200
. - (t=3)
ZStack
propose101x101
to bothchild1
andchild2
.child1
changes its size based on the proposed size.child2
appears (with default transition animation) based on the proposed size. - (t=4)
ZStack
propose102x102
to bothchild1
andchild2
.child1
andchild2
changes its size based on the proposed size. - (t=5)
ZStack
propose103x103
to bothchild1
andchild2
.child1
andchild2
changes its size based on the proposed size.
…
- (t=N)
ZStack
stops proposing new sizes to both views, and the transition animation forchild2
completes.
In essence, geometry group propagates the geometry information contuniously along the animation timeline, ensuring that subviews acquire and maintain the correct layout positions.