Quick Start
Build a simple AR app with persistent anchors in minutes.
Overview
This guide walks you through:
- •Creating an AR session
- •Saving a space with its world map
- •Placing and persisting anchors
- •Loading spaces and anchors on next launch
Basic Setup
import Hyperbasis
import ARKit
class ARManager {
private let storage = HBStorage()
private var currentSpace: HBSpace?
// On first anchor placement - create space
func createSpace(from session: ARSession) async throws {
let worldMap = try await session.currentWorldMap
let space = try HBSpace(name: "My Space", worldMap: worldMap)
try await storage.save(space)
currentSpace = space
}
// Save an anchor
func saveAnchor(transform: simd_float4x4, metadata: [String: AnyCodableValue]) async throws {
guard let space = currentSpace else { throw MyError.noSpace }
let anchor = HBAnchor(
spaceId: space.id,
transform: transform,
metadata: metadata
)
try await storage.save(anchor)
}
// On app launch - load and relocalize
func loadExistingSpace() async throws -> ARWorldMap? {
let spaces = try await storage.loadAllSpaces()
guard let mostRecent = spaces.max(by: { $0.updatedAt < $1.updatedAt }) else {
return nil
}
currentSpace = mostRecent
return try mostRecent.arWorldMap()
}
// Load anchors after relocalization
func loadAnchors() async throws -> [HBAnchor] {
guard let space = currentSpace else { return [] }
return try await storage.loadAnchors(spaceId: space.id)
}
}Create and Save a Space
Capture the current ARWorldMap and save it as a space:
// When you're ready to save (e.g., after placing some anchors)
let worldMap = try await arSession.currentWorldMap
let space = try HBSpace(name: "Living Room", worldMap: worldMap)
try await storage.save(space)Tip
Best practice is to save the space after the user places their first anchor, or when the tracking state indicates the environment is well-mapped.
Save Anchors with Metadata
Anchors store a 3D transform plus custom metadata:
let anchor = HBAnchor(
spaceId: currentSpace.id,
transform: entity.transform.matrix,
metadata: [
"type": .string("sticky_note"),
"text": .string("Remember to buy milk"),
"color": .string("yellow"),
"priority": .int(1),
"completed": .bool(false)
]
)
try await storage.save(anchor)Load and Relocalize
On next app launch, load the space and relocalize:
// 1. Load most recent space
let spaces = try await storage.loadAllSpaces()
let mostRecent = spaces.max(by: { $0.updatedAt < $1.updatedAt })
if let space = mostRecent {
// 2. Get ARWorldMap
let worldMap = try space.arWorldMap()
// 3. Configure AR session with world map
let config = ARWorldTrackingConfiguration()
config.initialWorldMap = worldMap
arSession.run(config, options: [.resetTracking, .removeExistingAnchors])
// 4. Wait for relocalization, then load anchors
let anchors = try await storage.loadAnchors(spaceId: space.id)
// 5. Recreate your objects
for anchor in anchors {
let transform = try anchor.simdTransform()
// Create your entity at this transform
}
}Full Integration Example
@MainActor
class PersistenceManager: ObservableObject {
private let storage: HBStorage
@Published private(set) var currentSpace: HBSpace?
@Published private(set) var isLoading = false
init() {
self.storage = HBStorage()
}
func createSpace(name: String?, worldMap: ARWorldMap) async throws -> HBSpace {
let space = try HBSpace(name: name, worldMap: worldMap)
try await storage.save(space)
self.currentSpace = space
return space
}
func loadMostRecentSpace() async throws -> HBSpace? {
isLoading = true
defer { isLoading = false }
let spaces = try await storage.loadAllSpaces()
let mostRecent = spaces.max(by: { $0.updatedAt < $1.updatedAt })
self.currentSpace = mostRecent
return mostRecent
}
func saveAnchor(transform: simd_float4x4, metadata: [String: AnyCodableValue]) async throws {
guard let space = currentSpace else { return }
let anchor = HBAnchor(
spaceId: space.id,
transform: transform,
metadata: metadata
)
try await storage.save(anchor)
}
func loadAnchors() async throws -> [HBAnchor] {
guard let space = currentSpace else { return [] }
return try await storage.loadAnchors(spaceId: space.id)
}
}Complete Files
Copy-paste ready files to get started quickly.
PersistenceManager.swift
A complete persistence manager you can drop into your project:
import Foundation
import ARKit
import Hyperbasis
/// Manages AR space and anchor persistence using Hyperbasis.
/// Drop this file into your project and use it as-is or customize.
@MainActor
public class PersistenceManager: ObservableObject {
// MARK: - Properties
private let storage: HBStorage
@Published public private(set) var currentSpace: HBSpace?
@Published public private(set) var isLoading = false
@Published public private(set) var error: Error?
// MARK: - Initialization
public init(config: HBStorageConfig = .default) {
self.storage = HBStorage(config: config)
}
// MARK: - Space Operations
/// Create a new space from the current AR world map.
public func createSpace(name: String?, worldMap: ARWorldMap) async throws -> HBSpace {
isLoading = true
defer { isLoading = false }
let space = try HBSpace(name: name, worldMap: worldMap)
try await storage.save(space)
self.currentSpace = space
return space
}
/// Load the most recently updated space.
public func loadMostRecentSpace() async throws -> HBSpace? {
isLoading = true
defer { isLoading = false }
let spaces = try await storage.loadAllSpaces()
let mostRecent = spaces.max(by: { $0.updatedAt < $1.updatedAt })
self.currentSpace = mostRecent
return mostRecent
}
/// Load a specific space by ID.
public func loadSpace(id: UUID) async throws -> HBSpace? {
isLoading = true
defer { isLoading = false }
let space = try await storage.loadSpace(id: id)
self.currentSpace = space
return space
}
/// Get all saved spaces.
public func loadAllSpaces() async throws -> [HBSpace] {
return try await storage.loadAllSpaces()
}
/// Delete a space and all its anchors.
public func deleteSpace(id: UUID) async throws {
try await storage.deleteSpace(id: id)
if currentSpace?.id == id {
currentSpace = nil
}
}
// MARK: - Anchor Operations
/// Save an anchor to the current space.
public func saveAnchor(
transform: simd_float4x4,
metadata: [String: AnyCodableValue] = [:]
) async throws -> HBAnchor {
guard let space = currentSpace else {
throw PersistenceError.noActiveSpace
}
let anchor = HBAnchor(
spaceId: space.id,
transform: transform,
metadata: metadata
)
try await storage.save(anchor)
return anchor
}
/// Load all anchors for the current space.
public func loadAnchors() async throws -> [HBAnchor] {
guard let space = currentSpace else { return [] }
return try await storage.loadAnchors(spaceId: space.id)
}
/// Update an existing anchor.
public func updateAnchor(_ anchor: HBAnchor) async throws {
try await storage.save(anchor)
}
/// Delete an anchor (soft delete).
public func deleteAnchor(id: UUID) async throws {
try await storage.deleteAnchor(id: id)
}
// MARK: - Utilities
/// Get local storage size in bytes.
public func storageSize() throws -> Int {
return try storage.localStorageSize()
}
/// Clear all local data.
public func clearAllData() throws {
try storage.clearLocalStorage()
currentSpace = nil
}
}
// MARK: - Errors
public enum PersistenceError: LocalizedError {
case noActiveSpace
public var errorDescription: String? {
switch self {
case .noActiveSpace:
return "No active space. Create or load a space first."
}
}
}Usage in SwiftUI
import SwiftUI
struct ContentView: View {
@StateObject private var persistence = PersistenceManager()
var body: some View {
ARViewContainer(persistence: persistence)
.overlay(alignment: .bottom) {
if persistence.isLoading {
ProgressView("Loading...")
.padding()
.background(.ultraThinMaterial)
.cornerRadius(8)
}
}
.task {
// Try to load existing space on launch
do {
if let space = try await persistence.loadMostRecentSpace() {
print("Loaded space: \(space.name ?? space.id.uuidString)")
}
} catch {
print("Failed to load space: \(error)")
}
}
}
}Next Steps
- •Architecture - Understand the data model
- •HBSpace - Deep dive into spaces
- •HBAnchor - Deep dive into anchors
- •Patterns - Integration patterns for SwiftUI apps