HyperbasisHyperbasisDocs
Home

Quick Start

Build a simple AR app with persistent anchors in minutes.

Overview

This guide walks you through:

  1. Creating an AR session
  2. Saving a space with its world map
  3. Placing and persisting anchors
  4. 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