Written against iOS 26/27 and visionOS 26/27 with RealityKit. Everything here assumes RealityKit, not SceneKit — see SceneKit Is Deprecated. Here's Your 12-Month Plan.
Almost every AR project we scope eventually hits the same question in week two: "the user placed the machine label / the sofa / the wayfinding sign yesterday — how does it come back in the same spot today?"
Placement is easy. Persistence is the hard part, and it is the feature most often discovered late, after the tracking code is already written. This tutorial covers the three mechanisms Apple gives you — ARWorldMap on iOS, WorldAnchor persistence on visionOS, and geo anchors for outdoors — plus the parts nobody documents: relocalization UX, cross-device sharing, and when to give up and re-anchor from scratch.
What "persistent" actually means
An ARKit anchor is a pose in a session's coordinate space. That space is invented fresh at every run() and has no relationship to the previous one. So "saving the anchor" means saving two different things:
- Your content state — which model, which scale, which metadata. This is ordinary app data; put it in your database or CloudKit.
- A spatial reference that lets a future session recover the same physical origin. This is the part ARKit provides, and the part that fails.
Keep them separate in your data model from day one. Content records should store a stable anchor identifier plus a transform relative to that anchor — never a raw world transform, which is meaningless across sessions.
Option 1: ARWorldMap on iOS
ARWorldMap is a snapshot of the session's feature-point map plus its anchors. Save it, reload it into a new configuration, and ARKit tries to relocalize: match what the camera sees now to the saved features. On success, your saved anchors reappear in the right physical place.
Saving
import ARKit
import RealityKit
func saveWorldMap(from session: ARSession, to url: URL) async throws {
let map = try await session.currentWorldMap()
let data = try NSKeyedArchiver.archivedData(
withRootObject: map,
requiringSecureCoding: true
)
try data.write(to: url, options: [.atomic])
}
Check session.currentFrame?.worldMappingStatus before you offer a Save button. Only .mapped — or at minimum .extending — is worth saving; .limited produces a map that will never relocalize. Surfacing that status as a simple "Scan a bit more of the room" prompt is the single highest-value piece of UX in this whole feature.
Restoring
func restore(mapAt url: URL, in session: ARSession) throws {
let data = try Data(contentsOf: url)
guard let map = try NSKeyedUnarchiver.unarchivedObject(
ofClass: ARWorldMap.self, from: data
) else { throw PersistenceError.badMap }
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
config.initialWorldMap = map
session.run(config, options: [.resetTracking, .removeExistingAnchors])
}
Then wait. Relocalization is not instant and it is not guaranteed. Watch ARCamera.TrackingState:
func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
switch camera.trackingState {
case .limited(.relocalizing):
ui.show("Point your device where you placed the content")
case .normal:
ui.hideRelocalizationPrompt()
case .limited(.initializing), .limited(.insufficientFeatures):
ui.show("Move slowly and look at a textured surface")
default:
break
}
}
Give it a deadline. In production we start a 20–30 second timer when relocalization begins. If tracking has not reached .normal, we stop trying, tell the user plainly that we could not find the previous setup, and offer to place content again. Users tolerate re-placement; they do not tolerate an app that appears frozen.
What breaks relocalization
- Lighting changes. A map built at 3pm often will not relocalize at 9pm under artificial light. Save separate maps per lighting condition if the space matters.
- Moved furniture. Feature points come from the environment. Rearrange 40% of a room and the map is stale.
- Blank surfaces. White walls, glass, polished floors — no features, no relocalization.
- Scale. World maps are room-scale. A warehouse needs several maps keyed to zones, not one giant map.
- Distance. Relocalize from roughly the same vantage point where the map was built. A saved "come stand here" photo thumbnail beats any amount of instructional text.
Option 2: WorldAnchor persistence on visionOS
visionOS handles this differently and, honestly, better. WorldTrackingProvider maintains a persistent store of WorldAnchors per user. You add an anchor, keep its id, and on a later launch the system hands the anchor back to you when it recognizes the space — no map file to manage.
import ARKit
import RealityKit
let session = ARKitSession()
let worldTracking = WorldTrackingProvider()
func start() async throws {
try await session.run([worldTracking])
for await update in worldTracking.anchorUpdates {
switch update.event {
case .added, .updated:
let anchor = update.anchor
guard let record = store.content(for: anchor.id) else { continue }
place(record, at: anchor.originFromAnchorTransform, isTracked: anchor.isTracked)
case .removed:
removeEntity(for: update.anchor.id)
}
}
}
func addPersistentContent(_ record: ContentRecord, at transform: simd_float4x4) async throws {
let anchor = WorldAnchor(originFromAnchorTransform: transform)
try await worldTracking.addAnchor(anchor)
store.save(record, anchorID: anchor.id) // your database, keyed by anchor.id
}
Two rules that save days of debugging:
- Anchors arrive asynchronously and untracked. On launch you may receive an anchor with
isTracked == false— the system knows it exists but has not localized it yet. Do not render content until it is tracked, or it will appear in the wrong place and then jump. - The store is the system's, not yours. Anchors can be removed by the system if the space changes. Handle
.removedby clearing your entity but keeping the content record, so the user can re-place rather than lose data.
Option 3: geo anchors, for outdoors
If the content lives outdoors in a covered city, ARGeoAnchor skips the whole problem: the anchor is a latitude/longitude/altitude, so it persists by definition and works across devices and users with no map file at all. Coverage is the constraint, not the code — see Location-Based AR with ARGeoAnchor.
Cross-device and multi-user persistence
A world map is a file, so it can be shared — that is how the classic multiplayer ARKit samples align two devices. But treat that carefully:
| Requirement | Mechanism |
|---|---|
| Same user, same device, later session | ARWorldMap on disk / WorldAnchor on visionOS |
| Same user, new device | Re-place, or sync the map via CloudKit and accept lower relocalization rates |
| Several users, same room, same time | Shared session — see Shared AR in 2026 |
| Several users, same room, different days | Server-stored world map per zone, plus a printed marker as a fallback origin |
| Outdoors, any user | ARGeoAnchor |
World maps are big — often several megabytes — and they are opaque binaries. Compress before upload, version them, and set an expiry policy; a six-month-old map of an office is worse than no map, because it fails slowly instead of failing fast.
The pragmatic fallback: a printed marker
For enterprise deployments where placement must be repeatable — factory line, retail fixture, lab bench — do not rely on feature-point relocalization at all. Put a printed image marker at a known point and use image tracking to define the origin. It relocalizes in under a second, in any lighting, on any device, for any user. It is unglamorous and it is what we recommend on most industrial projects.
Privacy and App Store review
World maps are a spatial reconstruction of a user's home or workplace. If you upload them, say so in your privacy policy and in your App Store privacy nutrition label, store them encrypted, and give users a way to delete them. Reviewers do ask, and enterprise security teams definitely ask. Keeping maps device-local unless there is a concrete sharing requirement is the safer default.
A build order that works
- Ship placement with no persistence. Confirm people actually want content to come back.
- Add device-local
ARWorldMapsave/restore with a hard relocalization timeout and an honest failure path. - Instrument it: log relocalization attempts, successes, and time-to-relocalize. If your success rate is under ~70% in the real environment, the answer is markers, not more tuning.
- Only then consider cloud sync, cross-device sharing, or multi-zone maps.
Next steps
- Apple's ARWorldMap documentation and Saving and Loading World Data.
- WorldTrackingProvider for the visionOS side.
- If your persistent content is room-scale furniture or fixtures, combine this with Room-Scale AR with RoomPlan.
Persistence is where AR pilots quietly stall: the demo worked, the second visit did not. RealityRogue's ARKit consultants have shipped relocalization in offices, warehouses, and retail spaces, and can tell you early whether your environment supports it or whether you should be budgeting for markers. Get in touch with your site conditions and use case.