Provides spatial alignment functionality in RealityKit using Immersal's PosePlugin and REST API. A Swift Package for visionOS 2.0+.
Important
This package requires visionOS Enterprise API access for camera functionality. You must include an Enterprise license file in your app bundle to use Enterprise APIs. See Apple's documentation and Building spatial experiences for business apps for details.
This package requires the PosePlugin static library from Immersal SDK:
- Download the Immersal SDK core package from https://developers.immersal.com
- Extract
libPosePlugin.afrom the SDK - Place it in your project:
YourApp/ ├── lib/ │ └── libPosePlugin.a ← Place the library here └── YourApp.xcodeproj - Configure your Xcode project:
- Add the library to your target's "Link Binary With Libraries" build phase
- In Build Settings, add
-lc++to "Other Linker Flags"
Note
The libPosePlugin.a file is not included in this repository due to licensing. You must obtain it from Immersal's developer portal.
- Platform: visionOS 2.0+
- Swift: 5.8+ (Swift Tools 6.0+, Language Mode 5)
- Xcode: 16.0+
- Open your project in Xcode
- Select File > Add Package Dependencies...
- Enter the package URL:
https://github.com/gaprot/ImmersalKit.git - Choose version rules and click Add Package
dependencies: [
.package(url: "https://github.com/gaprot/ImmersalKit.git", from: "1.0.0")
]import ImmersalKit
// Using PosePlugin localizer (on-device)
let immersalKit = ImmersalKit(
localizerType: .posePlugin,
arSessionManager: ARSessionManager()
)
// Using REST API localizer (cloud)
let immersalKit = ImmersalKit(
localizerType: .restApi,
arSessionManager: ARSessionManager(),
tokenProvider: BundleTokenProvider() // Load token from Info.plist
)To use ImmersalMapComponent in Reality Composer Pro, you need to copy template files:
- Copy these files to your RealityKitContent project:
Sources/ImmersalKit/RealityKitContentTemplates/ImmersalMapComponent.swift→YourApp/Packages/RealityKitContent/Sources/RealityKitContent/Sources/ImmersalKit/RealityKitContentTemplates/Entity+Extensions.swift→YourApp/Packages/RealityKitContent/Sources/RealityKitContent/
- Uncomment the code in the copied files
- Open your scene in Reality Composer Pro
- Select the Entity where you want to place your map
- In Inspector, click Add Component → ImmersalMapComponent
- Enter the Map ID (e.g., 127558)
- Place your AR content as children of the ImmersalMapComponent entity - they will automatically be positioned correctly when localized
// Register maps existing in RCP scene
if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
content.add(scene)
// Find and register all ImmersalMapComponents
scene.forEachDescendant(withComponent: ImmersalMapComponent.self) { entity, component in
immersalKit.mapManager.registerMap(mapEntity: entity, mapId: component.mapId)
}
}
// Load map data - map files must be in app bundle as {mapId}-*.bytes
immersalKit.mapManager.loadMap(mapId: 127558)Note
ImmersalKit automatically handles position transformation, but visibility control is the app's responsibility. You may want to hide maps until localization succeeds.
Map files must be included in your app bundle with the naming format:
- File format:
{mapId}-{name}.bytes - Example:
127558-RoomL.bytes
Download map files from Immersal Developer Portal.
// Start localization
Task {
do {
try await immersalKit.startLocalizing()
} catch {
print("Localization start error: \(error)")
}
}
// Stop localization
Task {
await immersalKit.stopLocalizing()
}// SwiftUI usage example
struct ContentView: View {
@State private var immersalKit: ImmersalKit
var body: some View {
VStack {
Text("Localizing: \(immersalKit.isLocalizing ? "Yes" : "No")")
if let result = immersalKit.lastResult {
Text("Confidence: \(result.confidence)")
Text("Position: \(result.position)")
}
}
}
}.posePlugin: Offline localization (on-device processing).restApi: Online localization (cloud processing)
Developer token is required when using REST API:
// Load from Info.plist
// Set token with "ImmersalToken" key in Info.plist
let tokenProvider = BundleTokenProvider()
// Static token
let tokenProvider = StaticTokenProvider(token: "your-token-here")
// Using Keychain
let tokenProvider = SecureTokenProvider()
tokenProvider.setToken("your-token-here")Control alignment precision:
// Default configuration
let config = ConfidenceBasedAlignmentConfiguration()
// Custom configuration
let config = ConfidenceBasedAlignmentConfiguration(
minimumConfidenceDelta: -2.0, // Recent confidence delta threshold
absoluteMinimumConfidence: 15.0, // Absolute minimum confidence
maxHistorySize: 5 // History size
)ImmersalKit provides comprehensive error handling:
do {
try await immersalKit.startLocalizing()
} catch ImmersalKitError.session(.permissionDenied) {
// Camera access permission error
print("Please allow camera access")
} catch ImmersalKitError.configuration(.missingRequiredConfiguration(let param)) {
// Configuration error
print("Missing required configuration: \(param)")
} catch ImmersalKitError.mapManagement(.mapNotFound(let mapId)) {
// Map not found
print("Map \(mapId) not found")
} catch {
// Other errors
print("Error: \(error.localizedDescription)")
}import SwiftUI
import ImmersalKit
import RealityKit
@main
struct MyARApp: App {
@State private var immersalKit: ImmersalKit
init() {
immersalKit = ImmersalKit(
localizerType: .posePlugin,
arSessionManager: ARSessionManager()
)
// Load initial maps
_ = immersalKit.mapManager.loadMap(mapId: 121385)
}
var body: some Scene {
WindowGroup {
ContentView(immersalKit: immersalKit)
}
ImmersiveSpace(id: "ARSpace") {
ARView(immersalKit: immersalKit)
}
}
}
struct ContentView: View {
let immersalKit: ImmersalKit
@State private var isLocalizing = false
var body: some View {
VStack {
Button(isLocalizing ? "Stop" : "Start") {
Task {
if isLocalizing {
await immersalKit.stopLocalizing()
} else {
try? await immersalKit.startLocalizing()
}
isLocalizing = immersalKit.isLocalizing
}
}
if let result = immersalKit.lastResult {
Text("Confidence: \(result.confidence, specifier: "%.1f")")
}
}
}
}If you're upgrading from an earlier version, please note these important API changes:
-
Removed APIs:
setSelectedMapIds()- Maps are now automatically selected when loadedsetInitialMapIds()in Builder - Load maps directly usingmapManager.loadMap()setMapIds()- UsemapManager.loadMap()instead
-
New behavior:
loadMap()now automatically makes the map available for localization- No need to separately select maps after loading
- Pros:
- Offline operation (no internet required)
- Low latency
- Provides actual confidence scores for accurate confidence-based alignment
- Cons:
- Map data must be included in app
- Increased app size
- Pros:
- No map files required for localization (only map IDs needed)
- Significantly smaller app size
- Cons:
- Internet connection required
- API latency
- Token management required
- Fixed confidence value (100.0) - REST API does not provide confidence information, limiting confidence-based alignment functionality
- Verify map files (
.bytes) are included in app bundle - Check file naming follows
{mapId}-*.bytesformat (e.g.,121385-mapname.bytes) - Ensure maps are registered before loading
- Confirm
loadMap()is called for each map you want to use
- Verify camera access permission is granted (Enterprise API required)
- Ensure maps are loaded correctly
- Create maps in more distinctive environments
- Adjust confidence control settings (
ConfidenceBasedAlignmentConfiguration)
- Verify
libPosePlugin.ais in the correct location - Check that the library is added to your target's build phases
After coordinate transformation, there is approximately a 25cm height offset that requires manual adjustment. This is currently handled with a hardcoded correction in the package.
A complete sample application is included in the Demo/ folder:
- Open
Demo/Demo.xcodeprojin Xcode - Select visionOS simulator or device
- Build and run
This project is licensed under the MIT License - see the LICENSE file for details.
This software includes code from Immersal SDK iOS Samples (MIT License).
For technical questions or issues, please contact us through GitHub Issues.