RealityKit Basics: Perform SwiftUI Animations

Starting in visionOS 26, we can perform SwiftUI animations in RealityKit.

Overview

SwiftUI has some pretty slick animation features that we can use across Apple platforms. Starting in visionOS 26, we can use these animation on our RealityKit entities. There are two main ways to do this.

Content animate

We can call content.animate in the make or update closure of RealityView. This is helpful when the animation is a side effect of something else happening in our app. For example, if we can to play an animation when a user changes a value.

content.animate {
  let scaler: Float = subjectAToggle ? 2.0 : 1.0
  subjectA.scale = .init(repeating: scaler)
}

Let’s create a SwiftUI toggle toggle that will update the scale of an entity. We’ll add state and a binding to hold our animation. Then add a Toggle view that uses this binding.

fileprivate struct Example109ExampleA: View {

    @State private var subjectA = Entity()
    @State private var subjectAToggle = false
    var subjectAToggleAnimation: Binding<Bool> {
        $subjectAToggle.animation(.easeInOut(duration: 1))
    }

    var body: some View {
        RealityView { content in

            let subjectA = createStepDemoBox("subjectA", true)
            self.subjectA = subjectA
            content.add(subjectA)

        } update: { content in

            content.animate {
                let scaler: Float = subjectAToggle ? 2.0 : 1.0
                subjectA.scale = .init(repeating: scaler)
            }

        }
        .toolbar {
            ToolbarItem(placement: .bottomOrnament, content: {
                Toggle(isOn: subjectAToggleAnimation, label: {
                    Text("Toggle Subject A")
                })
            })
        }
    }
}

Entity animate

We can also call Entity.animate. This type method can be a bit easier to use. In this example, we’ll create an entity with a Tap Component that animates the scale change.

fileprivate struct Example109ExampleB: View {

    // For this example, we'll use a state var. In a production app it makes more sense to keep entity state in components
    @State private var subjectBToggle = false

    var body: some View {
        RealityView { content in

            let subjectB = createStepDemoBox("subjectB", true)
            content.add(subjectB)

            let tap = TapGesture().onEnded({ [weak subjectB] _ in
                // We'll use a SwiftUI animation to scale the entity
                Entity.animate(.easeInOut(duration: 1), body: {
                    subjectBToggle.toggle()
                    let scaler: Float = subjectBToggle ? 2.0 : 1.0
                    subjectB?.scale = .init(repeating: scaler)
                })
            })
            let gesture = GestureComponent(tap)
            subjectB.components.set(gesture)

        }
    }
}

I use this approach much more often than content.animate as it seems much more approachable. We can place animations like this in gesture and event closures. While Example B uses SwiftUI state to keep track of our toggle value, it may make more sense to store that in a component. If this value is only used for animation/internal RealityKit work, then save it on a custom component. On the other hand, if the animation needs to be a side effect of something else in the app, storing it in state or a model may be better.

See also:

We can animate many properties on RealityKit components this way. But there are some complex values that well be tricky or impossible to animate with SwiftUI. In those cases we can use RealityKit animations or custom systems.

Credit: Apple, WWDC 2025 – Better together: SwiftUI and RealityKit

Example Code

struct Example109: View {
    @State private var subjectA = Entity()

    @State private var subjectAToggle = false
    var subjectAToggleAnimation: Binding<Bool> {
        $subjectAToggle.animation(.easeInOut(duration: 1))
    }

    @State private var subjectBToggle = false

    var body: some View {

        HStack() {
            Example109ExampleA()
            Example109ExampleB()
        }

    }
}

fileprivate struct Example109ExampleA: View {

    @State private var subjectA = Entity()
    @State private var subjectAToggle = false
    var subjectAToggleAnimation: Binding<Bool> {
        $subjectAToggle.animation(.easeInOut(duration: 1))
    }

    var body: some View {
        RealityView { content in

            let subjectA = createStepDemoBox("subjectA", true)
            self.subjectA = subjectA
            content.add(subjectA)

        } update: { content in

            content.animate {
                let scaler: Float = subjectAToggle ? 2.0 : 1.0
                subjectA.scale = .init(repeating: scaler)
            }

        }
        .toolbar {
            ToolbarItem(placement: .bottomOrnament, content: {
                Toggle(isOn: subjectAToggleAnimation, label: {
                    Text("Toggle Subject A")
                })
            })
        }
    }
}

fileprivate struct Example109ExampleB: View {

    // For this example, we'll use a state var. In a production app it makes more sense to keep entity state in components
    @State private var subjectBToggle = false

    var body: some View {
        RealityView { content in

            let subjectB = createStepDemoBox("subjectB", true)
            content.add(subjectB)

            let tap = TapGesture().onEnded({ [weak subjectB] _ in
                // We'll use a SwiftUI animation to scale the entity
                Entity.animate(.easeInOut(duration: 1), body: {
                    subjectBToggle.toggle()
                    let scaler: Float = subjectBToggle ? 2.0 : 1.0
                    subjectB?.scale = .init(repeating: scaler)
                })
            })
            let gesture = GestureComponent(tap)
            subjectB.components.set(gesture)

        }
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?