HBSpace
Represents a mapped physical environment containing an ARWorldMap.
Overview
HBSpace is the container for ARWorldMap data. It represents a physical location that has been scanned and can be relocalized.
public struct HBSpace: Codable, Identifiable, Equatable {
public let id: UUID
public var name: String?
public var worldMapData: Data
public let createdAt: Date
public var updatedAt: Date
}Properties
| Property | Type | Description |
|---|---|---|
id* | UUID | Unique identifier for the space |
name | String? | Human-readable name (e.g., 'Living Room') |
worldMapData* | Data | Serialized ARWorldMap (typically 5-50MB) |
createdAt* | Date | When the space was first created |
updatedAt* | Date | When the space was last modified |
Initialization
From ARWorldMap (typical usage)
let space = try HBSpace(
id: UUID(), // optional, defaults to new UUID
name: "My Room", // optional
worldMap: arWorldMap // ARWorldMap from ARSession
)From pre-serialized data (loading from storage)
let space = HBSpace(
id: existingId,
name: "My Room",
worldMapData: serializedData,
createdAt: originalDate,
updatedAt: modifiedDate
)Methods
Serialization
// Serialize ARWorldMap to Data
public static func serialize(worldMap: ARWorldMap) throws -> Data
// Deserialize Data back to ARWorldMap
public static func deserialize(worldMapData: Data) throws -> ARWorldMap
// Convenience: get ARWorldMap from space
public func arWorldMap() throws -> ARWorldMapMutation
// Update with new world map (also updates updatedAt)
public mutating func update(worldMap: ARWorldMap) throws
// Update name
public mutating func update(name: String?)Utilities
// Validate that worldMapData can be deserialized
public func validate() -> Bool
// Size in bytes
public var worldMapSize: Int
// Formatted size (e.g., "12.3 MB")
public var worldMapSizeFormatted: StringSerialization Details
ARWorldMap serialization uses NSKeyedArchiver with secure coding:
// Serialization
let data = try NSKeyedArchiver.archivedData(
withRootObject: worldMap,
requiringSecureCoding: true
)
// Deserialization
let worldMap = try NSKeyedUnarchiver.unarchivedObject(
ofClass: ARWorldMap.self,
from: data
)Note
ARWorldMap data is typically 5-50MB depending on environment complexity. Hyperbasis compresses this data by ~40% when storing.
World Map Size Guide
World map size depends on environment complexity and how much the user has scanned:
| Environment | Typical Size | After Compression | Notes |
|---|---|---|---|
| Small room (desk) | 5-10 MB | 3-6 MB | Limited features, quick scan |
| Medium room | 10-25 MB | 6-15 MB | Normal usage |
| Large room / office | 25-40 MB | 15-24 MB | Open floor plan |
| Outdoor / large area | 30-50+ MB | 18-30+ MB | Many features, takes longer |
Factors that increase size:
- •More textured surfaces (books, posters, patterns)
- •Longer scanning duration
- •Larger physical area covered
- •More feature points detected
Factors that decrease size:
- •Plain walls and surfaces
- •Quick, focused scans
- •Smaller area coverage
When to Capture World Maps
Save your world map at the right time for best relocalization results:
Checklist Before Saving
// Check these conditions before calling session.currentWorldMap
func isReadyToSave(frame: ARFrame) -> Bool {
// 1. Tracking must be normal
guard frame.camera.trackingState == .normal else {
return false
}
// 2. Should have detected some planes
let planeAnchors = frame.anchors.compactMap { $0 as? ARPlaneAnchor }
guard planeAnchors.count >= 2 else {
return false
}
// 3. World mapping status should be good
guard frame.worldMappingStatus == .mapped ||
frame.worldMappingStatus == .extending else {
return false
}
return true
}Best Practices
| Do | Don't |
|---|---|
Wait for tracking state .normal | Save immediately on app launch |
Check worldMappingStatus is .mapped | Save when tracking is .limited |
| Let user look around the space first | Save after scanning only one small area |
| Save after placing the first anchor | Save before any AR content is placed |
| Save when going to background | Wait too long (user might leave) |
World Mapping Status
switch frame.worldMappingStatus {
case .notAvailable:
// Too early, wait longer
statusLabel.text = "Initializing..."
case .limited:
// Some data, but not enough for reliable relocalization
statusLabel.text = "Move around to scan more"
case .extending:
// Good! Can save, but more scanning would help
statusLabel.text = "Good - keep scanning for better results"
case .mapped:
// Excellent! Best time to save
statusLabel.text = "Ready to save"
@unknown default:
break
}Save on Background
Always save the world map when the app goes to background:
NotificationCenter.default.addObserver(
forName: UIApplication.willResignActiveNotification,
object: nil,
queue: .main
) { [weak self] _ in
Task {
await self?.saveCurrentState()
}
}Relocalization Tips
For successful relocalization when the user returns:
- •Similar lighting - Dramatically different lighting can prevent recognition
- •Same physical space - User needs to be in the same room
- •Minimal changes - Major furniture rearrangement may break relocalization
- •Help the user - Show instructions to move the phone around
func showRelocalizationHelp() {
instructionLabel.text = "Point your camera around the room to recognize the space"
// Timeout and offer to start fresh
DispatchQueue.main.asyncAfter(deadline: .now() + 20) { [weak self] in
if self?.isRelocalized == false {
self?.showStartFreshOption()
}
}
}Error Handling
public enum HBSpaceError: LocalizedError {
case serializationFailed(underlying: Error?)
case deserializationFailed(underlying: Error?)
case invalidWorldMapData
}Usage Examples
Create and save a space
// Get world map from AR session
let worldMap = try await arSession.currentWorldMap
// Create space
let space = try HBSpace(name: "Living Room", worldMap: worldMap)
// Save to storage
try await storage.save(space)Load and relocalize
// Load most recent space
let spaces = try await storage.loadAllSpaces()
let mostRecent = spaces.max(by: { $0.updatedAt < $1.updatedAt })
if let space = mostRecent {
// Get world map for relocalization
let worldMap = try space.arWorldMap()
// Configure AR session
let config = ARWorldTrackingConfiguration()
config.initialWorldMap = worldMap
arSession.run(config, options: [.resetTracking])
}Update an existing space
// After environment has changed
var space = existingSpace
let newWorldMap = try await arSession.currentWorldMap
try space.update(worldMap: newWorldMap)
try await storage.save(space)