Timelines: Working with Notifications

Sending notifications from code to trigger a behavior, and sending notifications from a timeline to trigger code.

We can set up two-way communication between a code and an entity/timeline from Reality Composer Pro. Let’s start from the Swift side. We’ll send a notification that will trigger a behavior, which will run a timeline.

    func notifyTimeline(_ identifier: String) {
        if let scene = scene {
            NotificationCenter.default.post(
                name: NSNotification.Name("RealityKit.NotificationTrigger"),
                object: nil,
                userInfo: [
                    "RealityKit.NotificationTrigger.Scene": scene,
                    "RealityKit.NotificationTrigger.Identifier": identifier
                ]
            )
        }
    }
notifyTimeline("MoveRightMessage")

In Reality Composer Pro, we add a Behavior Component to the entity that should receive this. Then we select the Timeline to run.

Now when we call notifyTimeline("MoveRightMessage"), the red sphere will play the MoveRight timeline.

Notice the Notification action at the end of the timeline? We can use this to send another message, this time in the other direction. We just enter an identifier for the notification.

Note: Reality Composer Pro prompts us to select a Target entity. From what we can tell, this entity is not passed with the other notification data. For consistency, we’ll select the same entity the other actions used. Technically this action will fire without a Target set, but you may see a warnings in RCP.

We can listen for a RealityKit.NotificationTrigger using the onReceive modifier. This object contains the scene that sent the notification and the identifier. We can use Scene if we need to do something inside the RealityKit context. We use Identifier to determine what notification was sent.

.onReceive(NotificationCenter.default.publisher(for: Notification.Name("RealityKit.NotificationTrigger"))) { notification in
    guard
        let userInfo = notification.userInfo,
        let scene = userInfo["RealityKit.NotificationTrigger.Scene"] as? RealityKit.Scene,
        let identifier = userInfo["RealityKit.NotificationTrigger.Identifier"] as? String
    else { return }

    switch identifier {
    case "ReachedRight":
        print("Timeline sent: ReachedRight")
        isRight = true
    case "ReachedLeft":
        print("Timeline sent: ReachedLeft")
        isLeft = true
    default:
        print("Unknown message from timeline: \(identifier) in \(scene)")
        break
    }
}

Video Demo

Full Example Code

Make sure to check out the SimpleTimeline scene in the Reality Composer Pro bundle in the repo.

struct Example119: View {
    @Environment(\.realityKitScene) var scene

    @State var isLeft: Bool = true
    @State var isRight: Bool = false

    var body: some View {
        RealityView { content in
            guard let scene = try? await Entity(named: "SimpleTimeline", in: realityKitContentBundle) else { return }
            scene.position.y = -0.4
            content.add(scene)
        }
        .ornament(attachmentAnchor: .scene(.back), ornament: {
            VStack(spacing: 12) {
                HStack {
                    Button(action: {
                        notifyTimeline("MoveLeftMessage")
                        isLeft = false
                        isRight = false
                    }, label: {
                        Text("MoveLeft")
                    })

                    Spacer()

                    Button(action: {
                        notifyTimeline("MoveRightMessage")
                        isLeft = false
                        isRight = false
                    }, label: {
                        Text("MoveRight")
                    })
                }

                HStack {
                    Circle()
                        .fill(isLeft ? .green : .white)
                        .frame(width: 60, height: 60)
                    Spacer()
                    Circle()
                        .fill(isRight ? .green : .white)
                        .frame(width: 60, height: 60)

                }
                .frame(width: 300)
            }
            .padding()
            .glassBackgroundEffect()
        })
        // 2. Timelines can use the Notification action to send a message that we can receive here
        .onReceive(NotificationCenter.default.publisher(for: Notification.Name("RealityKit.NotificationTrigger"))) { notification in
            // We can unpack Scene and Identifier
            // We use Identifier to determine what notification was sent
            // We can use Scene if we need to do something inside the RealityKit context
            guard
                let userInfo = notification.userInfo,
                let scene = userInfo["RealityKit.NotificationTrigger.Scene"] as? RealityKit.Scene,
                let identifier = userInfo["RealityKit.NotificationTrigger.Identifier"] as? String
            else { return }

            switch identifier {
            case "ReachedRight":
                print("Timeline sent: ReachedRight")
                isRight = true
            case "ReachedLeft":
                print("Timeline sent: ReachedLeft")
                isLeft = true
            default:
                print("Unknown message from timeline: \(identifier) in \(scene)")
                break
            }
        }
    }

    // 1. Send notifications to trigger a behavior on an entity, which will run a timeline
    func notifyTimeline(_ identifier: String) {
        if let scene = scene {
            NotificationCenter.default.post(
                name: NSNotification.Name("RealityKit.NotificationTrigger"),
                object: nil,
                userInfo: [
                    "RealityKit.NotificationTrigger.Scene": scene,
                    "RealityKit.NotificationTrigger.Identifier": identifier
                ]
            )
        }
    }
}

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?