SwiftUI Notes 3: Transform Animations
02/06/2026
Transform modifiers change how a view looks — its position, size, rotation, or 3D angle — without changing its actual layout. SwiftUI provides four: .offset, .scaleEffect, .rotationEffect, and .rotation3DEffect. Stack them on the same view and animate them with a single state toggle.
| Modifier | What it does |
|---|---|
.offset | Move position |
.scaleEffect | Resize visually |
.rotationEffect | 2D spin |
.rotation3DEffect | 3D tilt |
I. The Setup
Every transform animation starts the same way. Create a boolean @State, toggle it on tap, wrap the toggle in withAnimation, and use the state to switch between two values in your transform modifiers.
@State var show = false
RoundedRectangle(cornerRadius: 30)
.frame(width: 300, height: 200)
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
}
}The view doesn't know or care what transforms you'll apply — it just flips between two states. You attach the transforms below.
II. The Four Transforms
1. Offset
Slides the view on the x and y axis. Like pushing a card across a table. The original layout position doesn't change — only where the view is drawn. Surrounding views don't reflow — offset is purely visual.
Positive y = down, negative y = up. Positive x = right, negative x = left.
// Moves up 200 points when show is true
.offset(y: show ? -200 : 0)
// Move both horizontally and vertically
.offset(x: show ? 50 : 0, y: show ? -100 : 0)2. Scale Effect
Makes the view bigger or smaller without changing its frame. 1.0 is the original size. 1.2 is 20% bigger. 0.5 is half. The frame stays the same — only the visual changes, which is why it works so well for animation.
Scales from the center by default. Add anchor: .top or .bottomLeading to scale from a corner.
// Grows 20% when tapped
.scaleEffect(show ? 1.2 : 1)
// Scale x and y independently
.scaleEffect(x: show ? 1.5 : 1, y: show ? 0.8 : 1)3. Rotation Effect
Rotates the view flat, like turning a steering wheel. Takes an Angle in degrees. Positive = clockwise, negative = counter-clockwise.
Use .degrees() shorthand: .rotationEffect(.degrees(show ? 30 : 0)). Full spin = 360 degrees.
// Tilts 30° clockwise when tapped
.rotationEffect(Angle(degrees: show ? 30 : 0))
// Rotate from a corner instead of center
.rotationEffect(
Angle(degrees: show ? 45 : 0),
anchor: .topLeading
)4. Rotation 3D Effect
Tilts the view in 3D space — like flipping a card toward or away from you. You set an angle and an axis. The axis tells SwiftUI which direction to rotate around.
.rotation3DEffect(
Angle(degrees: show ? 30 : 0),
axis: (x: 1, y: 0, z: 0) // tilts forward/back
)| Axis | What it does | Think of it as |
|---|---|---|
x: 1 | Tilts forward and backward | Nodding your head |
y: 1 | Swivels left and right | Shaking your head "no" |
z: 1 | Spins flat (same as rotationEffect) | Turning a steering wheel |
You can combine axes: axis: (x: 1, y: 1, z: 0) tilts diagonally. The perspective parameter controls how dramatic the 3D effect looks — 1 is default, lower values feel more extreme.
Transforms don't change layout. They only change how the view is drawn. That's what makes them safe and fast for animation.
III. Stacking Transforms
The real power is combining transforms on the same view. Each modifier adds its effect on top of the previous one. One state toggle drives all of them simultaneously.
RoundedRectangle(cornerRadius: 30)
.frame(width: 300, height: 200)
.foregroundColor(.purple)
.offset(y: show ? -200 : 0)
.scaleEffect(show ? 1.2 : 1)
.rotationEffect(Angle(degrees: show ? 30 : 0))
.rotation3DEffect(
Angle(degrees: show ? 30 : 0),
axis: (x: 1, y: 0, z: 0)
)
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
}
}When you tap, the card moves up, grows, tilts 2D, and tilts 3D — all in one smooth spring animation.
Order matters
SwiftUI applies modifiers top-to-bottom.
- Offset → Rotate: moves straight, then spins in the new position.
- Rotate → Offset: spins first, then moves along the rotated axis.
If you put .offset before .rotationEffect, the view moves first, then rotates in place. If you reverse them, the view rotates first, then the offset direction rotates with it — the view moves diagonally instead of straight up. For most card animations, put .offset first.
IV. Quick Reference
| Modifier | Parameters | Default anchor |
|---|---|---|
.offset(x:y:) | CGFloat for each axis | N/A |
.scaleEffect(_:anchor:) | CGFloat scale, UnitPoint | .center |
.rotationEffect(_:anchor:) | Angle, UnitPoint | .center |
.rotation3DEffect(_:axis:anchor:perspective:) | Angle, (x,y,z), UnitPoint, CGFloat | .center, 1.0 |
Common pitfalls
- Using
.frame()to resize instead of.scaleEffect()— frame changes cause layout reflow and janky animation. - Forgetting
withAnimation— the state changes instantly with no transition. - Putting transforms on a parent
VStack/HStack— all children inherit the transform, which is usually not what you want. - Setting
perspectiveto0inrotation3DEffect— the 3D effect disappears entirely.