Post

Replies

Boosts

Views

Activity

Reply to RealityView AR - anchored to the screen not the floor
// // ARUserAuth.swift // XRSandbox // // Created by Andy Wyatt on 3/15/26. // import SwiftUI import RealityKit import ARKit import AVFoundation import CoreMotion public struct ARUserAuth { public static var isAvailableAndAuthorizedByUser: Bool { guard ARWorldTrackingConfiguration.isSupported else { return false } return hasCameraAuthorization && hasMotionAuthorization } public static var hasCameraAuthorization: Bool { let authStatus = AVCaptureDevice.authorizationStatus(for: .video) if authStatus == .notDetermined { requestUserAuthorizationForCamera() } return authStatus == .authorized } public static func requestUserAuthorizationForCamera() { print("ARUserAuth: Camera access not determined, requesting user authorization") AVCaptureDevice.requestAccess(for: .video) { granted in if granted { print("ARUserAuth: App access to Camera authorized by user") // since these are modal popups, request Motion & Fitness only after Camera access granted if coreMotionAuthorizationStatus == .notDetermined { print("ARUserAuth: Motion & Fitness access not determined, requesting user authorization") self.requestUserAuthorizationForMotion() } } else { print("ARUserAuth: App access to Camera denied by user") } } } public static var coreMotionAuthorizationStatus: CMAuthorizationStatus { //TODO: is this the best way to get this? return CMPedometer.authorizationStatus() } public static var hasMotionAuthorization: Bool { let authStatus = coreMotionAuthorizationStatus return authStatus == .authorized } public static func requestUserAuthorizationForMotion() { CMMotionActivityManager().queryActivityStarting(from: .now, to: .now, to: .main) { activity, error in // shows the permission prompt if no authorization print("ARUserAuth: App access to Motion & Fitness authorized by user") } } }
Topic: Graphics & Games SubTopic: RealityKit Tags:
May ’26
Reply to RealityView AR - anchored to the screen not the floor
FYI: // // ContentView.swift // XRSandbox // // Created by Andy Wyatt on 3/15/26. // import SwiftUI import RealityKit struct ContentView: View { private let rootEntity = Entity() var body: some View { RealityView { content in content.add(rootEntity) } update: { content in updateARState(&content) } .ignoresSafeArea() .onAppear() { configureVirtualCamera() rootEntity.addChild(makeThing()) } .onTapGesture { spinThing() } .overlay { overlayView } } //MARK: - overlay with toggles at bottom @State private var arToggleState: Bool = false @State private var showARView: Bool = false @State private var perspectiveCameraInAR: Bool = false private var overlayView: some View { VStack { Spacer() VStack { Toggle("Enable AR", isOn: $arToggleState) .onChange(of: arToggleState) { handleARToggle() } // If PerspectiveCamera is in the RealityView entity heirarchy // the content is anchored to the screen rather than the floor Toggle("Keep PerspectiveCamera in AR", isOn: $perspectiveCameraInAR) .disabled(arToggleState) // can only change when not in AR } .padding() .background(Color(uiColor: UIColor.systemBackground).opacity(0.5)) // invisible when not in AR .cornerRadius(10) } .padding() } private func handleARToggle() { if arToggleState, ARUserAuth.isAvailableAndAuthorizedByUser == false { arToggleState = false return } if showARView != arToggleState { showARView = arToggleState } } //MARK: - transition to/from AR private let xrAnchor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: [0.2, 0.2])) static private var anchorStateChangedSubscription: EventSubscription? = nil static private var inSpatialTrackingCameraMode: Bool = false private func updateARState(_ content: inout RealityViewCameraContent) { if showARView == true && Self.inSpatialTrackingCameraMode == false { Self.inSpatialTrackingCameraMode = true transitionToAR(&content) } else if showARView == false && Self.inSpatialTrackingCameraMode == true { Self.inSpatialTrackingCameraMode = false transitionFromAR(&content) } } //TODO: not always getting anchor event after several in/out/in/out of AR cycles - why? // experiment with sequence of events here and in transitionFromAR() // maybe more reliable with a custom spatial tracking session? private func transitionToAR(_ content: inout RealityViewCameraContent) { content.remove(rootEntity) content.add(xrAnchor) content.camera = .spatialTracking print("spatialTracking camera activated: \(content.camera) •••••••••••••") Self.anchorStateChangedSubscription = content.subscribe(to: SceneEvents.AnchoredStateChanged.self) { event in print("••• anchor changed ••• anchor: '\(event.anchor.name)', isAnchored: \(event.isAnchored), anchor transform: \(String(describing: event.anchor.convert(transform: event.anchor.transform, to: nil))) •••••••••••••") if event.anchor == xrAnchor, event.isAnchored { if perspectiveCameraInAR == false { // If PerspectiveCamera is in the RealityView entity heirarchy // the content is anchored to the screen rather than the floor rootEntity.removeChild(cameraEntity) } rootEntity.position.y = 1.0 //1m above the anchor xrAnchor.addChild(rootEntity) } } } private func transitionFromAR(_ content: inout RealityViewCameraContent) { Self.anchorStateChangedSubscription?.cancel() Self.anchorStateChangedSubscription = nil content.remove(xrAnchor) content.camera = .virtual print("virtual camera activated: \(content.camera) •••••••••••••") configureVirtualCamera() //adds back to scene if removed in transitionToXR(_:) rootEntity.removeFromParent() // remove from xrAnchor rootEntity.position = .zero content.add(rootEntity) } //MARK: - virtual camera private let cameraEntity = PerspectiveCamera() private func configureVirtualCamera() { rootEntity.addChild(cameraEntity) cameraEntity.name = "virtual camera" cameraEntity.position = [0, 0, 2] // matches the default virtual camera cameraEntity.look(at: .zero, from: cameraEntity.position, relativeTo: nil) } //MARK: - thing func makeThing() -> Entity { let thing = ModelEntity(mesh: .generateCylinder(height: 0.5, radius: 0.2), materials: [SimpleMaterial(color: .purple, isMetallic: true)]) thing.transform.rotation = .init(angle: 45.0, axis: [0,1,1]) thing.name = "thing" return thing } func spinThing() { guard let thing = rootEntity.findEntity(named: "thing") else { return } let spin = SpinAction(revolutions: 1, localAxis: [0, 1, 1], timingFunction: .easeInOut) let spinAnimation = try! AnimationResource.makeActionAnimation(for: spin, duration: 2.0, bindTarget: .transform, repeatMode: .none) thing.playAnimation(spinAnimation) } } #Preview { ContentView() }
Topic: Graphics & Games SubTopic: RealityKit Tags:
May ’26
Reply to RealityView AR - anchored to the screen not the floor
// // ARUserAuth.swift // XRSandbox // // Created by Andy Wyatt on 3/15/26. // import SwiftUI import RealityKit import ARKit import AVFoundation import CoreMotion public struct ARUserAuth { public static var isAvailableAndAuthorizedByUser: Bool { guard ARWorldTrackingConfiguration.isSupported else { return false } return hasCameraAuthorization && hasMotionAuthorization } public static var hasCameraAuthorization: Bool { let authStatus = AVCaptureDevice.authorizationStatus(for: .video) if authStatus == .notDetermined { requestUserAuthorizationForCamera() } return authStatus == .authorized } public static func requestUserAuthorizationForCamera() { print("ARUserAuth: Camera access not determined, requesting user authorization") AVCaptureDevice.requestAccess(for: .video) { granted in if granted { print("ARUserAuth: App access to Camera authorized by user") // since these are modal popups, request Motion & Fitness only after Camera access granted if coreMotionAuthorizationStatus == .notDetermined { print("ARUserAuth: Motion & Fitness access not determined, requesting user authorization") self.requestUserAuthorizationForMotion() } } else { print("ARUserAuth: App access to Camera denied by user") } } } public static var coreMotionAuthorizationStatus: CMAuthorizationStatus { //TODO: is this the best way to get this? return CMPedometer.authorizationStatus() } public static var hasMotionAuthorization: Bool { let authStatus = coreMotionAuthorizationStatus return authStatus == .authorized } public static func requestUserAuthorizationForMotion() { CMMotionActivityManager().queryActivityStarting(from: .now, to: .now, to: .main) { activity, error in // shows the permission prompt if no authorization print("ARUserAuth: App access to Motion & Fitness authorized by user") } } }
Topic: Graphics & Games SubTopic: RealityKit Tags:
Replies
Boosts
Views
Activity
May ’26
Reply to RealityView AR - anchored to the screen not the floor
// // XRSandboxApp.swift // XRSandbox // // Created by Andy Wyatt on 3/6/26. // import SwiftUI @main struct XRSandboxApp: App { var body: some Scene { WindowGroup { ContentView() } } }
Topic: Graphics & Games SubTopic: RealityKit Tags:
Replies
Boosts
Views
Activity
May ’26
Reply to RealityView AR - anchored to the screen not the floor
FYI: // // ContentView.swift // XRSandbox // // Created by Andy Wyatt on 3/15/26. // import SwiftUI import RealityKit struct ContentView: View { private let rootEntity = Entity() var body: some View { RealityView { content in content.add(rootEntity) } update: { content in updateARState(&content) } .ignoresSafeArea() .onAppear() { configureVirtualCamera() rootEntity.addChild(makeThing()) } .onTapGesture { spinThing() } .overlay { overlayView } } //MARK: - overlay with toggles at bottom @State private var arToggleState: Bool = false @State private var showARView: Bool = false @State private var perspectiveCameraInAR: Bool = false private var overlayView: some View { VStack { Spacer() VStack { Toggle("Enable AR", isOn: $arToggleState) .onChange(of: arToggleState) { handleARToggle() } // If PerspectiveCamera is in the RealityView entity heirarchy // the content is anchored to the screen rather than the floor Toggle("Keep PerspectiveCamera in AR", isOn: $perspectiveCameraInAR) .disabled(arToggleState) // can only change when not in AR } .padding() .background(Color(uiColor: UIColor.systemBackground).opacity(0.5)) // invisible when not in AR .cornerRadius(10) } .padding() } private func handleARToggle() { if arToggleState, ARUserAuth.isAvailableAndAuthorizedByUser == false { arToggleState = false return } if showARView != arToggleState { showARView = arToggleState } } //MARK: - transition to/from AR private let xrAnchor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: [0.2, 0.2])) static private var anchorStateChangedSubscription: EventSubscription? = nil static private var inSpatialTrackingCameraMode: Bool = false private func updateARState(_ content: inout RealityViewCameraContent) { if showARView == true && Self.inSpatialTrackingCameraMode == false { Self.inSpatialTrackingCameraMode = true transitionToAR(&content) } else if showARView == false && Self.inSpatialTrackingCameraMode == true { Self.inSpatialTrackingCameraMode = false transitionFromAR(&content) } } //TODO: not always getting anchor event after several in/out/in/out of AR cycles - why? // experiment with sequence of events here and in transitionFromAR() // maybe more reliable with a custom spatial tracking session? private func transitionToAR(_ content: inout RealityViewCameraContent) { content.remove(rootEntity) content.add(xrAnchor) content.camera = .spatialTracking print("spatialTracking camera activated: \(content.camera) •••••••••••••") Self.anchorStateChangedSubscription = content.subscribe(to: SceneEvents.AnchoredStateChanged.self) { event in print("••• anchor changed ••• anchor: '\(event.anchor.name)', isAnchored: \(event.isAnchored), anchor transform: \(String(describing: event.anchor.convert(transform: event.anchor.transform, to: nil))) •••••••••••••") if event.anchor == xrAnchor, event.isAnchored { if perspectiveCameraInAR == false { // If PerspectiveCamera is in the RealityView entity heirarchy // the content is anchored to the screen rather than the floor rootEntity.removeChild(cameraEntity) } rootEntity.position.y = 1.0 //1m above the anchor xrAnchor.addChild(rootEntity) } } } private func transitionFromAR(_ content: inout RealityViewCameraContent) { Self.anchorStateChangedSubscription?.cancel() Self.anchorStateChangedSubscription = nil content.remove(xrAnchor) content.camera = .virtual print("virtual camera activated: \(content.camera) •••••••••••••") configureVirtualCamera() //adds back to scene if removed in transitionToXR(_:) rootEntity.removeFromParent() // remove from xrAnchor rootEntity.position = .zero content.add(rootEntity) } //MARK: - virtual camera private let cameraEntity = PerspectiveCamera() private func configureVirtualCamera() { rootEntity.addChild(cameraEntity) cameraEntity.name = "virtual camera" cameraEntity.position = [0, 0, 2] // matches the default virtual camera cameraEntity.look(at: .zero, from: cameraEntity.position, relativeTo: nil) } //MARK: - thing func makeThing() -> Entity { let thing = ModelEntity(mesh: .generateCylinder(height: 0.5, radius: 0.2), materials: [SimpleMaterial(color: .purple, isMetallic: true)]) thing.transform.rotation = .init(angle: 45.0, axis: [0,1,1]) thing.name = "thing" return thing } func spinThing() { guard let thing = rootEntity.findEntity(named: "thing") else { return } let spin = SpinAction(revolutions: 1, localAxis: [0, 1, 1], timingFunction: .easeInOut) let spinAnimation = try! AnimationResource.makeActionAnimation(for: spin, duration: 2.0, bindTarget: .transform, repeatMode: .none) thing.playAnimation(spinAnimation) } } #Preview { ContentView() }
Topic: Graphics & Games SubTopic: RealityKit Tags:
Replies
Boosts
Views
Activity
May ’26
Reply to RealityView AR - anchored to the screen not the floor
Sorry for my slow response. I just created FB22780449 with the source code of my simple test project. Please see my comments in that post about the content sometimes not appearing where expected when returning from AR, and sometimes not finding an anchor when going in&out&in&out&in&out&in&out of AR like a squeezebox. (c;
Topic: Graphics & Games SubTopic: RealityKit Tags:
Replies
Boosts
Views
Activity
May ’26