---
title: Building  Apps for visionOS with Swift
tags:  #visionos  
author: [satoshi0212](https://convert.docswell.com/user/satoshi0212)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/4EQ5M15XJP.jpg?width=480
description: Building  Apps for visionOS with Swift by satoshi0212
published: March 22, 24
canonical: https://convert.docswell.com/s/satoshi0212/ZGXD48-2024-03-22-111031
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/4EQ5M15XJP.jpg)

try! Swift Tokyo 2024
Building Apps for visionOS
with Swift
Satoshi Hattori
@shmdevelop
1


# Page. 2

![Page Image](https://bcdn.docswell.com/page/KJ49VR9271.jpg)

Satoshi Hattori
xR Engineer at Cyber AI Productions
xR Guild Leader, AR Next Experts
at Cyber Agent
Owner of visionOS Engineer Meetup
Interests
ㅤvisionOS、Apple Vision Pro、Unreal Engine、NDI
Youtube favorites
Shiba Inu, Renovating old houses, SF6, M:tG
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/LE1K3QKKJG.jpg)

visionOS 30 Days Challenge
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/GEWDPMDPJ2.jpg)

4


# Page. 5

![Page Image](https://bcdn.docswell.com/page/47ZGKQG6E3.jpg)

5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/YJ6YQ6YL7V.jpg)

6


# Page. 7

![Page Image](https://bcdn.docswell.com/page/GJ5GR6GMJ4.jpg)

7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/9E2YDMYRER.jpg)

https://github.com/satoshi0212/visionOS̲30Days
8


# Page. 9

![Page Image](https://bcdn.docswell.com/page/D7YG2XG57M.jpg)

I think this session to be a great value!
9


# Page. 10

![Page Image](https://bcdn.docswell.com/page/VENG81G4J8.jpg)

I&#039;ve spent over a month understanding and organizing the content of this talk.
1 month * 300 people = 25 years&#039; worth of time
into just 20 minutes.
The source code has been shared on GitHub.
https://github.com/satoshi0212/MySpatialTimer
If you have not yet started to develop visionOS apps, you may wonder what I&#039;m talking
about, but you&#039;ll come back to this content when you actually want to place objects in
space!
10


# Page. 11

![Page Image](https://bcdn.docswell.com/page/Y79WK5W473.jpg)

I&#039;ve spent over a month understanding and organizing the content of this talk.
1 month * 300 people = 25 years&#039; worth of time
into just 20 minutes.
The source code has been shared on GitHub.
https://github.com/satoshi0212/MySpatialTimer
If you have not yet started to develop visionOS apps, you may wonder what I&#039;m talking
about, but you&#039;ll come back to this content when you actually want to place objects in
space!
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/G78NRYNVJD.jpg)

I&#039;ve spent over a month understanding and organizing the content of this talk.
1 month * 300 people = 25 years&#039; worth of time
into just 20 minutes.
The source code has been shared on GitHub.
https://github.com/satoshi0212/MySpatialTimer
If you have not yet started to develop visionOS apps, you may wonder what I&#039;m talking
about, but you&#039;ll come back to this content when you actually want to place objects in
space!
12


# Page. 13

![Page Image](https://bcdn.docswell.com/page/L7LPK4PRJR.jpg)

My Spatial Timer
13


# Page. 14

![Page Image](https://bcdn.docswell.com/page/4EM42K4PJW.jpg)

My Spatial Timer
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/PERWMXWYJ9.jpg)

RealityKit / Entities / Components / Systems / Meshes /
Materials / Image-based lighting / Shadows / Foveation /
Dynamic content scaling / Spatial Audio / Video / Model3D /
RealityView / Attachments / Animation / Particle emitters /
Collision detection / Physics / Anchoring / Portals /
Reality Composer Pro / USD / MaterialX
15


# Page. 16

![Page Image](https://bcdn.docswell.com/page/P7XPV6P4JX.jpg)

RealityKit / Entities / Components / Systems / Meshes /
Materials / Image-based lighting / Shadows / Foveation /
Dynamic content scaling / Spatial Audio / Video / Model3D /
RealityView / Attachments / Animation / Particle emitters /
Collision detection / Physics / Anchoring / Portals /
Reality Composer Pro / USD / MaterialX
16


# Page. 17

![Page Image](https://bcdn.docswell.com/page/37KDQ1DPED.jpg)

Immersive Space
17


# Page. 18

![Page Image](https://bcdn.docswell.com/page/LJ3RPQR4E5.jpg)

App
18


# Page. 19

![Page Image](https://bcdn.docswell.com/page/8JDYL9Y5JG.jpg)

App
Scene
Scene
Scene
19


# Page. 20

![Page Image](https://bcdn.docswell.com/page/VEP62Z6D78.jpg)

App
Window
Scene
Scene
20


# Page. 21

![Page Image](https://bcdn.docswell.com/page/27V3DW3GJQ.jpg)

App
Window
Volume
Scene
21


# Page. 22

![Page Image](https://bcdn.docswell.com/page/5JG3YDYDEL.jpg)

App
Window
Volume
ImmersiveSpace
22


# Page. 23

![Page Image](https://bcdn.docswell.com/page/47Q5M1MXEP.jpg)

Placement Location Updates
23


# Page. 24

![Page Image](https://bcdn.docswell.com/page/KE49VRV2J1.jpg)

Placement Location Updates
RealityKit attachments
24


# Page. 25

![Page Image](https://bcdn.docswell.com/page/L71K3Q3KEG.jpg)

Placement Location Updates
RealityKit attachments
WorldAnchor
25


# Page. 26

![Page Image](https://bcdn.docswell.com/page/G7WDPMPPE2.jpg)

Placement Location Updates
RealityKit attachments
WorldAnchor
26


# Page. 27

![Page Image](https://bcdn.docswell.com/page/4JZGKQK673.jpg)

RealityView
27


# Page. 28

![Page Image](https://bcdn.docswell.com/page/YE6YQ6QLJV.jpg)

28


# Page. 29

![Page Image](https://bcdn.docswell.com/page/GE5GR6RME4.jpg)

29


# Page. 30

![Page Image](https://bcdn.docswell.com/page/972YDMDR7R.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
}
}
}
30


# Page. 31

![Page Image](https://bcdn.docswell.com/page/DJYG2X25JM.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content, attachments in
// make
} update: { content, attachments in
// update
} attachments: {
Attachment(id: &quot;&quot;) {
Text(&quot;&quot;)
}
}
}
}
31


# Page. 32

![Page Image](https://bcdn.docswell.com/page/V7NG8184E8.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
}
}
}
32


# Page. 33

![Page Image](https://bcdn.docswell.com/page/YJ9WK5K4J3.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
let cubeEntity = ModelEntity(
mesh: .generateBox(size: 0.5),
materials: [SimpleMaterial(color: .blue, isMetallic: false)]
)
cubeEntity.position = SIMD3&lt;Float&gt;(x: -0.5, y: 1.0, z: -1.0)
content.add(cubeEntity)
}
}
}
33


# Page. 34

![Page Image](https://bcdn.docswell.com/page/GJ8NRYRVED.jpg)

34


# Page. 35

![Page Image](https://bcdn.docswell.com/page/LJLPK4KRER.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
let cylinderEntity = ModelEntity(
mesh: .generateCylinder(height: 0.01, radius: 0.3),
materials: [SimpleMaterial(color: .blue, isMetallic: false)]
)
cylinderEntity.position = SIMD3&lt;Float&gt;(x: -0.5, y: 1.0, z: -1.0)
content.add(cylinderEntity)
}
}
}
35


# Page. 36

![Page Image](https://bcdn.docswell.com/page/47M42K2PEW.jpg)

36


# Page. 37

![Page Image](https://bcdn.docswell.com/page/P7RWMXMYE9.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
var body: some View {
RealityView { content in
let cylinderEntity = ModelEntity(
mesh: .generateCylinder(height: 0.01, radius: 0.3),
materials: [SimpleMaterial(color: .init(red: 0, green: 0, blue: 1, alpha: 0.5), isMetallic: false)]
)
cylinderEntity.position = SIMD3&lt;Float&gt;(x: -0.5, y: 1.0, z: -1.0)
let rotationQuaternionX = simd_quatf(angle: .pi / 2, axis: SIMD3&lt;Float&gt;(1, 0, 0))
let rotationQuaternionY = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 1, 0))
let rotationQuaternionZ = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 0, 1))
cylinderEntity.orientation = rotationQuaternionX * rotationQuaternionY * rotationQuaternionZ
content.add(cylinderEntity)
}
}
}
37


# Page. 38

![Page Image](https://bcdn.docswell.com/page/PJXPV6V4EX.jpg)

38


# Page. 39

![Page Image](https://bcdn.docswell.com/page/3JKDQ1QP7D.jpg)

ViewModel
39


# Page. 40

![Page Image](https://bcdn.docswell.com/page/LE3RPQP475.jpg)

import Observation
import RealityKit
@Observable
class ViewModel {
}
40


# Page. 41

![Page Image](https://bcdn.docswell.com/page/8EDYL9L5EG.jpg)

import Observation
import RealityKit
@Observable
class ViewModel {
private let rootEntity = Entity()
func setupRootEntity() -&gt; Entity {
return rootEntity
}
func addCylinder() {
let cylinderEntity = ModelEntity(
mesh: .generateCylinder(height: 0.01, radius: 0.3),
materials: [SimpleMaterial(color: .init(red: 0, green: 0, blue: 1, alpha: 0.5), isMetallic: false)]
)
cylinderEntity.position = SIMD3&lt;Float&gt;(x: -0.5, y: 1.0, z: -1.0)
let rotationQuaternionX = simd_quatf(angle: .pi / 2, axis: SIMD3&lt;Float&gt;(1, 0, 0))
let rotationQuaternionY = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 1, 0))
let rotationQuaternionZ = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 0, 1))
cylinderEntity.orientation = rotationQuaternionX * rotationQuaternionY * rotationQuaternionZ
rootEntity.addChild(cylinderEntity)
}
}
41


# Page. 42

![Page Image](https://bcdn.docswell.com/page/V7P62Z2DJ8.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
@State private var viewModel = ViewModel()
var body: some View {
RealityView { content in
content.add(viewModel.setupRootEntity())
viewModel.addCylinder()
}
}
}
42


# Page. 43

![Page Image](https://bcdn.docswell.com/page/2JV3DWDGEQ.jpg)

import Observation
import RealityKit
@Observable
class ViewModel {
private let rootEntity = Entity()
func setupRootEntity() -&gt; Entity {
return rootEntity
}
func addCylinder() {
let cylinderEntity = ModelEntity(
mesh: .generateCylinder(height: 0.01, radius: 0.3),
materials: [SimpleMaterial(color: .init(red: 0, green: 0, blue: 1, alpha: 0.5), isMetallic: false)]
)
cylinderEntity.position = SIMD3&lt;Float&gt;(x: -0.5, y: 1.0, z: -1.0)
let rotationQuaternionX = simd_quatf(angle: .pi / 2, axis: SIMD3&lt;Float&gt;(1, 0, 0))
let rotationQuaternionY = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 1, 0))
let rotationQuaternionZ = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 0, 1))
cylinderEntity.orientation = rotationQuaternionX * rotationQuaternionY * rotationQuaternionZ
rootEntity.addChild(cylinderEntity)
}
}
43


# Page. 44

![Page Image](https://bcdn.docswell.com/page/5EG3YDVD7L.jpg)

import SwiftUI
import RealityKit
struct ImmersiveView: View {
@State private var viewModel = ViewModel()
var body: some View {
RealityView { content in
content.add(viewModel.setupRootEntity())
viewModel.addCylinder()
}
}
}
44


# Page. 45

![Page Image](https://bcdn.docswell.com/page/4JQ5M16X7P.jpg)

45


# Page. 46

![Page Image](https://bcdn.docswell.com/page/K749VR42E1.jpg)

ARKitSession
46


# Page. 47

![Page Image](https://bcdn.docswell.com/page/LJ1K3Q4K7G.jpg)

import
import
import
import
ARKit
Observation
QuartzCore
RealityKit
@Observable
class ViewModel {
private let rootEntity = Entity()
private let arkitSession = ARKitSession()
private let worldTracking = WorldTrackingProvider()
private let placementLocation = Entity()
func setupRootEntity() -&gt; Entity {
rootEntity.addChild(placementLocation)
return rootEntity
}
@MainActor
func runARKitSession() async {
do {
try await arkitSession.run([worldTracking])
} catch {
return
}
}
...
}
47


# Page. 48

![Page Image](https://bcdn.docswell.com/page/GJWDPMXP72.jpg)

import
import
import
import
ARKit
Observation
QuartzCore
RealityKit
@Observable
class ViewModel {
private let rootEntity = Entity()
private let arkitSession = ARKitSession()
private let worldTracking = WorldTrackingProvider()
private let placementLocation = Entity()
func setupRootEntity() -&gt; Entity {
rootEntity.addChild(placementLocation)
return rootEntity
}
@MainActor
func runARKitSession() async {
do {
try await arkitSession.run([worldTracking])
} catch {
return
}
}
...
}
48


# Page. 49

![Page Image](https://bcdn.docswell.com/page/4EZGKQ66J3.jpg)

import
import
import
import
ARKit
Observation
QuartzCore
RealityKit
@Observable
class ViewModel {
private let rootEntity = Entity()
private let arkitSession = ARKitSession()
private let worldTracking = WorldTrackingProvider()
private let placementLocation = Entity()
func setupRootEntity() -&gt; Entity {
rootEntity.addChild(placementLocation)
return rootEntity
}
@MainActor
func runARKitSession() async {
do {
try await arkitSession.run([worldTracking])
} catch {
return
}
}
...
}
49


# Page. 50

![Page Image](https://bcdn.docswell.com/page/Y76YQ62LEV.jpg)

@MainActor
func processDeviceAnchorUpdates() async {
await run(function: self.queryAndProcessLatestDeviceAnchor, withFrequency: 90)
}
@MainActor
private func queryAndProcessLatestDeviceAnchor() async {
guard worldTracking.state == .running else { return }
let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())
guard let deviceAnchor, deviceAnchor.isTracked else { return }
let matrix = deviceAnchor.originFromAnchorTransform
let forward = simd_float3(0, 0, -1)
let cameraForward = simd_act(matrix.rotation, forward)
let front = SIMD3&lt;Float&gt;(x: cameraForward.x, y: cameraForward.y, z: cameraForward.z)
let length: Float = 0.5
let offset = length * simd_normalize(front)
placementLocation.position = matrix.position + offset
placementLocation.orientation = matrix.rotation
}
50


# Page. 51

![Page Image](https://bcdn.docswell.com/page/G75GR62M74.jpg)

@MainActor
func processDeviceAnchorUpdates() async {
await run(function: self.queryAndProcessLatestDeviceAnchor, withFrequency: 90)
}
@MainActor
private func queryAndProcessLatestDeviceAnchor() async {
guard worldTracking.state == .running else { return }
let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())
guard let deviceAnchor, deviceAnchor.isTracked else { return }
let matrix = deviceAnchor.originFromAnchorTransform
let forward = simd_float3(0, 0, -1)
let cameraForward = simd_act(matrix.rotation, forward)
let front = SIMD3&lt;Float&gt;(x: cameraForward.x, y: cameraForward.y, z: cameraForward.z)
let length: Float = 0.5
let offset = length * simd_normalize(front)
placementLocation.position = matrix.position + offset
placementLocation.orientation = matrix.rotation
}
51


# Page. 52

![Page Image](https://bcdn.docswell.com/page/9J2YDM4RJR.jpg)

extension ViewModel {
@MainActor
func run(function: () async -&gt; Void, withFrequency hz: UInt64) async {
while true {
if Task.isCancelled {
return
}
// Sleep for 1 s / hz before calling the function.
let nanoSecondsToSleep: UInt64 = NSEC_PER_SEC / hz
do {
try await Task.sleep(nanoseconds: nanoSecondsToSleep)
} catch {
// Sleep fails when the Task is cancelled. Exit the loop.
return
}
await function()
}
}
}
52


# Page. 53

![Page Image](https://bcdn.docswell.com/page/DEYG2XM5EM.jpg)

@MainActor
func processDeviceAnchorUpdates() async {
await run(function: self.queryAndProcessLatestDeviceAnchor, withFrequency: 90)
}
@MainActor
private func queryAndProcessLatestDeviceAnchor() async {
guard worldTracking.state == .running else { return }
let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())
guard let deviceAnchor, deviceAnchor.isTracked else { return }
let matrix = deviceAnchor.originFromAnchorTransform
let forward = simd_float3(0, 0, -1)
let cameraForward = simd_act(matrix.rotation, forward)
let front = SIMD3&lt;Float&gt;(x: cameraForward.x, y: cameraForward.y, z: cameraForward.z)
let length: Float = 0.5
let offset = length * simd_normalize(front)
placementLocation.position = matrix.position + offset
placementLocation.orientation = matrix.rotation
}
53


# Page. 54

![Page Image](https://bcdn.docswell.com/page/VJNG81W478.jpg)

@MainActor
func processDeviceAnchorUpdates() async {
await run(function: self.queryAndProcessLatestDeviceAnchor, withFrequency: 90)
}
@MainActor
private func queryAndProcessLatestDeviceAnchor() async {
guard worldTracking.state == .running else { return }
let deviceAnchor = worldTracking.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())
guard let deviceAnchor, deviceAnchor.isTracked else { return }
let matrix = deviceAnchor.originFromAnchorTransform
let forward = simd_float3(0, 0, -1)
let cameraForward = simd_act(matrix.rotation, forward)
let front = SIMD3&lt;Float&gt;(x: cameraForward.x, y: cameraForward.y, z: cameraForward.z)
let length: Float = 0.5
let offset = length * simd_normalize(front)
placementLocation.position = matrix.position + offset
placementLocation.orientation = matrix.rotation
}
54


# Page. 55

![Page Image](https://bcdn.docswell.com/page/YE9WK5X4E3.jpg)

func addMarker() {
let entity = ModelEntity(
mesh: .generateCylinder(height: 0.01, radius: 0.1),
materials: [SimpleMaterial(color: .init(red: 0, green: 0, blue: 1, alpha: 0.5), isMetallic: false)]
)
entity.components.set(InputTargetComponent())
entity.generateCollisionShapes(recursive: true)
let rotationQuaternionX = simd_quatf(angle: .pi / 2, axis: SIMD3&lt;Float&gt;(1, 0, 0))
let rotationQuaternionY = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 1, 0))
let rotationQuaternionZ = simd_quatf(angle: .pi, axis: SIMD3&lt;Float&gt;(0, 0, 1))
entity.orientation = rotationQuaternionX * rotationQuaternionY * rotationQuaternionZ
placementLocation.addChild(entity)
}
55


# Page. 56

![Page Image](https://bcdn.docswell.com/page/GE8NRY2V7D.jpg)

struct ImmersiveView: View {
var body: some View {
RealityView { content in
content.add(viewModel.setupRootEntity())
viewModel.addMarker()
Task {
await viewModel.runARKitSession()
}
}
.task {
await viewModel.processDeviceAnchorUpdates()
}
}
}
56


# Page. 57

![Page Image](https://bcdn.docswell.com/page/LELPK42R7R.jpg)

57


# Page. 58

![Page Image](https://bcdn.docswell.com/page/4JM42K8P7W.jpg)

func addPlaceHolder() {
let entity = ModelEntity(
mesh: .generateCylinder(height: 0.01, radius: 0.1),
materials: [SimpleMaterial(color: .init(red: 1, green: 0, blue: 0, alpha: 1), isMetallic: false)]
)
entity.transform = placementLocation.transform
let rotationQuaternionX = simd_quatf(angle: .pi / 2, axis: SIMD3&lt;Float&gt;(1, 0, 0))
entity.orientation *= rotationQuaternionX
rootEntity.addChild(entity)
}
58


# Page. 59

![Page Image](https://bcdn.docswell.com/page/PJRWMX5Y79.jpg)

struct ImmersiveView: View {
var body: some View {
RealityView { content in
...
}
.gesture(SpatialTapGesture()
.targetedToAnyEntity()
.onEnded { _ in
viewModel.addPlaceHolder()
})
}
}
59


# Page. 60

![Page Image](https://bcdn.docswell.com/page/PEXPV6K47X.jpg)

60


# Page. 61

![Page Image](https://bcdn.docswell.com/page/3EKDQ15PJD.jpg)

🎉
61


# Page. 62

![Page Image](https://bcdn.docswell.com/page/L73RPQK4J5.jpg)

Placement Location Updates
RealityKit attachments
WorldAnchor
62


# Page. 63

![Page Image](https://bcdn.docswell.com/page/87DYL9357G.jpg)

Placement Location Updates
RealityKit attachments
WorldAnchor
63


# Page. 64

![Page Image](https://bcdn.docswell.com/page/VJP62Z4DE8.jpg)

Timer UI and functions
64


# Page. 65

![Page Image](https://bcdn.docswell.com/page/2EV3DWXG7Q.jpg)

TimerView
65


# Page. 66

![Page Image](https://bcdn.docswell.com/page/57G3YDRDJL.jpg)

TimerManager
TimerModel
TimerModel
TimerModel
66


# Page. 67

![Page Image](https://bcdn.docswell.com/page/4EQ5M1VXJP.jpg)

Attachments
67


# Page. 68

![Page Image](https://bcdn.docswell.com/page/KJ49VRM271.jpg)

RealityView { content, attachments in
...
} update: { update, attachments in
for timerModel in timerManager.timerModels {
if let attachment = attachments.entity(for: timerModel.id),
let placeHolder = viewModel.getTargetEntity(name: timerModel.id.uuidString) {
if !placeHolder.children.contains(attachment) {
placeHolder.addChild(attachment)
}
}
}
} attachments: {
ForEach(timerManager.timerModels) { timerModel in
Attachment(id: timerModel.id) {
TimerView(viewModel: viewModel, timerManager: timerManager, timerModel: timerModel)
}
}
}
68


# Page. 69

![Page Image](https://bcdn.docswell.com/page/LE1K3Q8KJG.jpg)

RealityView { content, attachments in
...
} update: { update, attachments in
for timerModel in timerManager.timerModels {
if let attachment = attachments.entity(for: timerModel.id),
let placeHolder = viewModel.getTargetEntity(name: timerModel.id.uuidString) {
if !placeHolder.children.contains(attachment) {
placeHolder.addChild(attachment)
}
}
}
} attachments: {
ForEach(timerManager.timerModels) { timerModel in
Attachment(id: timerModel.id) {
TimerView(viewModel: viewModel, timerManager: timerManager, timerModel: timerModel)
}
}
}
69


# Page. 70

![Page Image](https://bcdn.docswell.com/page/GEWDPMZPJ2.jpg)

RealityView { content, attachments in
...
} update: { update, attachments in
for timerModel in timerManager.timerModels {
if let attachment = attachments.entity(for: timerModel.id),
let placeHolder = viewModel.getTargetEntity(name: timerModel.id.uuidString) {
if !placeHolder.children.contains(attachment) {
placeHolder.addChild(attachment)
}
}
}
} attachments: {
ForEach(timerManager.timerModels) { timerModel in
Attachment(id: timerModel.id) {
TimerView(viewModel: viewModel, timerManager: timerManager, timerModel: timerModel)
}
}
}
70


# Page. 71

![Page Image](https://bcdn.docswell.com/page/47ZGKQ16E3.jpg)

func addPlaceHolder() {
guard let timerManager = self.timerManager else { return }
let timerModel = timerManager.makeTimerModel()
timerManager.addTimerModel(timerModel: timerModel)
let entity = ModelEntity(
mesh: .generateSphere(radius: 0),
materials: [SimpleMaterial(color: .init(red: 1, green: 1, blue: 1, alpha: 0), isMetallic: false)]
)
entity.name = timerModel.id.uuidString
entity.transform = placementLocation.transform
rootEntity.addChild(entity)
}
71


# Page. 72

![Page Image](https://bcdn.docswell.com/page/YJ6YQ6LL7V.jpg)

72


# Page. 73

![Page Image](https://bcdn.docswell.com/page/GJ5GR61MJ4.jpg)

🎉
73


# Page. 74

![Page Image](https://bcdn.docswell.com/page/LE3RPQ1975.jpg)

Placement Location Updates
RealityKit attachments
WorldAnchor
74


# Page. 75

![Page Image](https://bcdn.docswell.com/page/8EDYL9X8EG.jpg)

Placement Location Updates
RealityKit attachments
WorldAnchor
75


# Page. 76

![Page Image](https://bcdn.docswell.com/page/V7P62ZPWJ8.jpg)

76


# Page. 77

![Page Image](https://bcdn.docswell.com/page/2JV3DW29EQ.jpg)

The manner of linking with WorldAnchor
77


# Page. 78

![Page Image](https://bcdn.docswell.com/page/5EG3YDR47L.jpg)

private var anchoredObjects: [UUID: Entity] = [:]
private var objectsBeingAnchored: [UUID: Entity] = [:]
private func attachObjectToWorldAnchor(_ object: Entity) async {
let anchor = await WorldAnchor(originFromAnchorTransform: object.transformMatrix(relativeTo: nil))
objectsBeingAnchored[anchor.id] = object
do {
try await worldTracking.addAnchor(anchor)
} catch {
print(&quot;Failed to add world anchor \(anchor.id) with error: \(error).&quot;)
objectsBeingAnchored.removeValue(forKey: anchor.id)
await object.removeFromParent()
return
}
}
78


# Page. 79

![Page Image](https://bcdn.docswell.com/page/4JQ5M1VR7P.jpg)

private var anchoredObjects: [UUID: Entity] = [:]
private var objectsBeingAnchored: [UUID: Entity] = [:]
private func attachObjectToWorldAnchor(_ object: Entity) async {
let anchor = await WorldAnchor(originFromAnchorTransform: object.transformMatrix(relativeTo: nil))
objectsBeingAnchored[anchor.id] = object
do {
try await worldTracking.addAnchor(anchor)
} catch {
print(&quot;Failed to add world anchor \(anchor.id) with error: \(error).&quot;)
objectsBeingAnchored.removeValue(forKey: anchor.id)
await object.removeFromParent()
return
}
}
79


# Page. 80

![Page Image](https://bcdn.docswell.com/page/K749VRMQE1.jpg)

func addPlaceHolder() {
guard let timerManager = self.timerManager else { return }
let timerModel = timerManager.makeTimerModel()
timerManager.addTimerModel(timerModel: timerModel)
let entity = ModelEntity(
mesh: .generateSphere(radius: 0),
materials: [SimpleMaterial(color: .init(red: 1, green: 1, blue: 1, alpha: 0), isMetallic: false)]
)
entity.name = timerModel.id.uuidString
entity.transform = placementLocation.transform
rootEntity.addChild(entity)
Task {
await attachObjectToWorldAnchor(entity)
}
}
80


# Page. 81

![Page Image](https://bcdn.docswell.com/page/LJ1K3Q8L7G.jpg)

RealityView { content, attachments in
...
} update: { update, attachments in
...
} attachments: {
...
}
.task {
await viewModel.processWorldAnchorUpdates()
}
.task {
await viewModel.processDeviceAnchorUpdates()
}
.gesture(SpatialTapGesture()
.targetedToAnyEntity()
.onEnded { _ in
viewModel.addPlaceHolder()
})
81


# Page. 82

![Page Image](https://bcdn.docswell.com/page/GJWDPMZQ72.jpg)

@MainActor
func processWorldAnchorUpdates() async {
for await anchorUpdate in worldTracking.anchorUpdates {
process(anchorUpdate)
}
}
82


# Page. 83

![Page Image](https://bcdn.docswell.com/page/4EZGKQ13J3.jpg)

@MainActor
private func process(_ anchorUpdate: AnchorUpdate&lt;WorldAnchor&gt;) {
let anchor = anchorUpdate.anchor
switch anchorUpdate.event {
case .added:
if let objectBeingAnchored = objectsBeingAnchored[anchor.id] {
objectsBeingAnchored.removeValue(forKey: anchor.id)
anchoredObjects[anchor.id] = objectBeingAnchored
} else {
if anchoredObjects[anchor.id] == nil {
Task {
await removeAnchorWithID(anchor.id)
}
}
}
fallthrough
case .updated:
if let object = anchoredObjects[anchor.id] {
object.position = anchor.originFromAnchorTransform.translation
object.orientation = anchor.originFromAnchorTransform.rotation
object.isEnabled = anchor.isTracked
}
case .removed:
if let object = anchoredObjects[anchor.id] {
object.removeFromParent()
}
anchoredObjects.removeValue(forKey: anchor.id)
}
}
83


# Page. 84

![Page Image](https://bcdn.docswell.com/page/Y76YQ6LZEV.jpg)

@MainActor
private func process(_ anchorUpdate: AnchorUpdate&lt;WorldAnchor&gt;) {
let anchor = anchorUpdate.anchor
switch anchorUpdate.event {
case .added:
if let objectBeingAnchored = objectsBeingAnchored[anchor.id] {
objectsBeingAnchored.removeValue(forKey: anchor.id)
anchoredObjects[anchor.id] = objectBeingAnchored
} else {
if anchoredObjects[anchor.id] == nil {
Task {
await removeAnchorWithID(anchor.id)
}
}
}
fallthrough
case .updated:
if let object = anchoredObjects[anchor.id] {
object.position = anchor.originFromAnchorTransform.translation
object.orientation = anchor.originFromAnchorTransform.rotation
object.isEnabled = anchor.isTracked
}
case .removed:
if let object = anchoredObjects[anchor.id] {
object.removeFromParent()
}
anchoredObjects.removeValue(forKey: anchor.id)
}
}
84


# Page. 85

![Page Image](https://bcdn.docswell.com/page/G75GR61974.jpg)

@MainActor
private func process(_ anchorUpdate: AnchorUpdate&lt;WorldAnchor&gt;) {
let anchor = anchorUpdate.anchor
switch anchorUpdate.event {
case .added:
if let objectBeingAnchored = objectsBeingAnchored[anchor.id] {
objectsBeingAnchored.removeValue(forKey: anchor.id)
anchoredObjects[anchor.id] = objectBeingAnchored
} else {
if anchoredObjects[anchor.id] == nil {
Task {
await removeAnchorWithID(anchor.id)
}
}
}
fallthrough
case .updated:
if let object = anchoredObjects[anchor.id] {
object.position = anchor.originFromAnchorTransform.translation
object.orientation = anchor.originFromAnchorTransform.rotation
object.isEnabled = anchor.isTracked
}
case .removed:
if let object = anchoredObjects[anchor.id] {
object.removeFromParent()
}
anchoredObjects.removeValue(forKey: anchor.id)
}
}
85


# Page. 86

![Page Image](https://bcdn.docswell.com/page/9J2YDM15JR.jpg)

@MainActor
private func process(_ anchorUpdate: AnchorUpdate&lt;WorldAnchor&gt;) {
let anchor = anchorUpdate.anchor
switch anchorUpdate.event {
case .added:
if let objectBeingAnchored = objectsBeingAnchored[anchor.id] {
objectsBeingAnchored.removeValue(forKey: anchor.id)
anchoredObjects[anchor.id] = objectBeingAnchored
} else {
if anchoredObjects[anchor.id] == nil {
Task {
await removeAnchorWithID(anchor.id)
}
}
}
fallthrough
case .updated:
if let object = anchoredObjects[anchor.id] {
object.position = anchor.originFromAnchorTransform.translation
object.orientation = anchor.originFromAnchorTransform.rotation
object.isEnabled = anchor.isTracked
}
case .removed:
if let object = anchoredObjects[anchor.id] {
object.removeFromParent()
}
anchoredObjects.removeValue(forKey: anchor.id)
}
}
86


# Page. 87

![Page Image](https://bcdn.docswell.com/page/DEYG2XZ6EM.jpg)

87


# Page. 88

![Page Image](https://bcdn.docswell.com/page/VJNG81Y178.jpg)

🎉
88


# Page. 89

![Page Image](https://bcdn.docswell.com/page/YE9WK5PYE3.jpg)

Demo
89


# Page. 90

![Page Image](https://bcdn.docswell.com/page/GE8NRYDK7D.jpg)

Demo Video
90


# Page. 91

![Page Image](https://bcdn.docswell.com/page/LELPK4MP7R.jpg)

Wrap up
Placement Location Updates
RealityKit attachments
https://github.com/satoshi0212/MySpatialTimer
WorldAnchor
and more...
Collision Group
ScenePhase
Data Save / Load
https://github.com/satoshi0212/visionOS_30Days
91


# Page. 92

![Page Image](https://bcdn.docswell.com/page/4JM42KY27W.jpg)

Wrap up
Placement Location Updates
RealityKit attachments
https://github.com/satoshi0212/MySpatialTimer
WorldAnchor
and more...
Collision Group
ScenePhase
Data Save / Load
https://github.com/satoshi0212/visionOS_30Days
92


