Complete visionOS implementation for tracking Logitech Muse stylus with real-time position updates, button input, and haptic feedback.
- visionOS 26+
- Logitech Muse stylus
- Xcode 26+
Add to Info.plist:
<key>NSAccessoryTrackingUsageDescription</key>
<string>tracking accessory movements to carve into virtual clay</string>// In WindowsDemoApp.swift
ImmersiveSpace(id: "LogitechMuse") {
ImmersiveStylusView()
}
.immersionStyle(selection: $selectedImmersionStyle, in: .mixed, .progressive, .full)- Open Vision Pro Settings β Bluetooth
- Pair Logitech Muse
- Launch app
- Open "Logitech Muse" immersive space
- β Real-time 6DOF tracking - Position + Orientation
- β Visual indicator - 2mm white sphere at stylus tip
- β Button input - Primary (Green) / Secondary (Blue)
- β Haptic feedback - Vibration on button press
- β Auto cleanup - Handles connect/disconnect gracefully
// CRITICAL: Store session to prevent deallocation!
// SpatialTrackingSession.run() is blocking - if local var only,
// it might be deallocated and tracking stops
@State private var trackingSession: SpatialTrackingSession?
.task {
let session = SpatialTrackingSession()
trackingSession = session // Keep alive!
await session.run(configuration)
}RootEntity
ββ AnchorEntity (.accessory) β ARKit tracks this
ββ ModelEntity (tip) β Moves automatically with anchor!
input.buttons[.stylusPrimaryButton]?.pressedInput.pressedDidChangeHandler = { _, _, pressed in
Task { @MainActor in // CRITICAL: Main thread for UI!
if pressed {
changeTipColor(to: .systemGreen)
playHaptic()
}
}
}Solution: Store session in @State:
@State private var trackingSession: SpatialTrackingSession?
trackingSession = session // In .taskSolution:
- Check stylus is connected via Bluetooth
- Verify Info.plist permissions
- Wait ~200 frames after connection
Solution: Add as child of anchor, not root:
anchor.addChild(tipSphere) // β
CORRECT
root.addChild(tipSphere) // β WRONG - won't move!let tipSphere = ModelEntity(
mesh: .generateSphere(radius: 0.002), // 2mm
materials: [material]
) // CRITICAL: Create AnchorEntity with accessory anchoring
// This tells ARKit to track this entity's position to match the stylus
let anchor = AnchorEntity(
.accessory(from: source, location: location),
trackingMode: .predicted, // Options: .predicted, .continuous, .once
physicsSimulation: .none
)CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), // 0.0-1.0
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) // 0.0-1.0