RealityKit Basics: Exploring SwiftUI Animations

Selecting from a short list of animations. Animating several RealityKit values.

Overview

We already learned how to Perform SwiftUI Animations in RealityKit. Now let’s look at some of the options we have. This post covers two concepts.

  1. Animating properties from various RealityKit components
  2. Selecting SwiftUI animations

Animating Components

As we saw in the last post, not all components and values can be animated. Apple shared a list in their WWDC session WWDC 2025 – Better together: SwiftUI and RealityKit

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

Let’s take a look at three examples. We’ll keep it simple and animate based a boolean value. The content.animate block is triggered anytime we change the value of subjectAToggle.

  • Animate part of the transform for the entity, scale in this case
  • Animate the base color of a material
  • Animate the angles for a spotlight
RealityView { content in
...
} update: { content in
    content.animate {
        // Animate part of the transfrm
        let scaler: Float = subjectAToggle ? 2.0 : 1.0
        subjectA.scale = .init(repeating: scaler)

        // Animate a material value
        if var material = subjectA.components[ModelComponent.self]?.materials.first as? PhysicallyBasedMaterial {
            material.baseColor.tint = UIColor(subjectAToggle ? .stepRed : .stepGreen)
            subjectA.components[ModelComponent.self]?.materials[0] = material
        }

        // Animate a light value
        if let spotLightEntity = subjectA.findEntity(named: "SpotLight") {
            spotLightEntity.components[SpotLightComponent.self]?.innerAngleInDegrees = subjectAToggle ? 45.0 : 15.0
            spotLightEntity.components[SpotLightComponent.self]?.innerAngleInDegrees = subjectAToggle ? 60.0 : 20.0
        }
    }
}

Selecting Animations

Exactly how the components are animated depends on on the animation that we provide to subjectAToggleAnimation binding.

var selectedAnimation: Animation { selectedAnim.animation }
var subjectAToggleAnimation: Binding<Bool> {
  $subjectAToggle.animation(selectedAnimation)
}

Let’s mock up a few from the presets

    enum AnimChoice {
        case easeInOut
        case smooth
        case snappy
        case bouncy
        case bouncyExtra
        case springBounce

        var animation: Animation {
            switch self {
            case .easeInOut:
                return .easeInOut(duration: 1)
            case .springBounce:
                return .spring(.bouncy(duration: 2.0, extraBounce: 0.1), blendDuration: 0.2)                
            case .smooth:
                return .smooth
            case .snappy:
                return .snappy
            case .bouncy:
                return .bouncy
            case .bouncyExtra:
                return .bouncy(duration: 1.0, extraBounce: 0.1)
            }
        }
    }

We can use this to set the type of animation we want to test, then toggle the value. Check out the video for a quick demo of each animation.

Spend some time with these animations. Many of them have duration or other values you can provide to tailor them to your needs.

Full Example Code

struct Example110: View {

    @State private var subjectA = Entity()
    @State private var subjectAToggle = false
    enum AnimChoice {
        case easeInOut
        case smooth
        case snappy
        case bouncy
        case bouncyExtra
        case springBounce

        var animation: Animation {
            switch self {
            case .easeInOut:
                return .easeInOut(duration: 1)
            case .smooth:
                return .smooth
            case .snappy:
                return .snappy
            case .bouncy:
                return .bouncy
            case .bouncyExtra:
                return .bouncy(duration: 1.0, extraBounce: 0.1)
            case .springBounce:
                return .spring(.bouncy(duration: 2.0, extraBounce: 0.1), blendDuration: 0.2)
            }
        }
    }

    @State private var selectedAnim: AnimChoice = .easeInOut
    var selectedAnimation: Animation { selectedAnim.animation }
    var subjectAToggleAnimation: Binding<Bool> {
        $subjectAToggle.animation(selectedAnimation)
    }

    var body: some View {
        RealityView { content in

            guard let scene = try? await Entity(named: "AnimatedComponents", in: realityKitContentBundle) else { return }
            scene.position.y = -0.4
            content.add(scene)
            guard let subjectA = scene.findEntity(named: "SubjectA") else { return }
            self.subjectA = subjectA


        } update: { content in

            content.animate {
                // Animate part of the transfrm
                let scaler: Float = subjectAToggle ? 2.0 : 1.0
                subjectA.scale = .init(repeating: scaler)

                // Animate a material value
                if var material = subjectA.components[ModelComponent.self]?.materials.first as? PhysicallyBasedMaterial {
                    material.baseColor.tint = UIColor(subjectAToggle ? .stepRed : .stepGreen)
                    subjectA.components[ModelComponent.self]?.materials[0] = material
                }

                // Animate a light value
                if let spotLightEntity = subjectA.findEntity(named: "SpotLight") {
                    spotLightEntity.components[SpotLightComponent.self]?.innerAngleInDegrees = subjectAToggle ? 45.0 : 15.0
                    spotLightEntity.components[SpotLightComponent.self]?.innerAngleInDegrees = subjectAToggle ? 60.0 : 20.0
                }
            }

        }
        .ornament(attachmentAnchor: .scene(.trailing), ornament: {
            VStack(alignment: .leading, spacing: 8) {
                Button(action: { selectedAnim = .easeInOut }) {
                    Label("EaseInOut", systemImage: selectedAnim == .easeInOut ? "checkmark.circle.fill" : "circle")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                Button(action: { selectedAnim = .springBounce }) {
                    Label("Spring", systemImage: selectedAnim == .springBounce ? "checkmark.circle.fill" : "circle")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                Button(action: { selectedAnim = .smooth }) {
                    Label("Smooth", systemImage: selectedAnim == .smooth ? "checkmark.circle.fill" : "circle")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                Button(action: { selectedAnim = .snappy }) {
                    Label("Snappy", systemImage: selectedAnim == .snappy ? "checkmark.circle.fill" : "circle")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                Button(action: { selectedAnim = .bouncy }) {
                    Label("Bouncy", systemImage: selectedAnim == .bouncy ? "checkmark.circle.fill" : "circle")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                Button(action: { selectedAnim = .bouncyExtra }) {
                    Label("Bouncy+ ", systemImage: selectedAnim == .bouncyExtra ? "checkmark.circle.fill" : "circle")
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
            }
            .frame(width: 160)
            .padding()
            .glassBackgroundEffect()
            // Yep, ornament on an ornament. What are you going to do? Call the ornament police?
            .ornament(attachmentAnchor: .parent(.top), contentAlignment: .bottom, ornament: {
                HStack {
                    Toggle(isOn: subjectAToggleAnimation, label: {
                        Text("Toggle")
                    })
                    .toggleStyle(.button)
                    .padding()
                }
                .glassBackgroundEffect()
            })
        })
    }
}

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?