Skip to content

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.

ModifierWhat it does
.offsetMove position
.scaleEffectResize visually
.rotationEffect2D spin
.rotation3DEffect3D 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
)
AxisWhat it doesThink of it as
x: 1Tilts forward and backwardNodding your head
y: 1Swivels left and rightShaking your head "no"
z: 1Spins 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

ModifierParametersDefault anchor
.offset(x:y:)CGFloat for each axisN/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 perspective to 0 in rotation3DEffect — the 3D effect disappears entirely.