Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

Can not remove final World Anchor

I’ve been having some issues removing anchors. I can add anchors with no issue. They will be there the next time I run the scene. I can also get updates when ARKit sends them. I can remove anchors, but not all the time. The method I’m using is to call removeAnchor() on the data provider.

worldTracking.removeAnchor(forID: uuid)
// Yes, I have also tried `removeAnchor(_ worldAnchor: WorldAnchor)`

This works if there are more than one anchor in a scene. When I’m down to one remaining anchor, I can remove it. It seems to succeed (does not raise an error) but the next time I run the scene the removed anchor is back. This only happens when there is only one remaining anchor.

do {
  // This always run, but it doesn't seem to "save" the removal when there is only one anchor left.
  try await worldTracking.removeAnchor(forID: uuid)
} catch {
  // I have never seen this block fire!
  print("Failed to remove world anchor \(uuid) with error: \(error).")
}

I posted a video on my website if you want to see it happening. https://stepinto.vision/labs/lab-051-issues-with-world-tracking/

Here is the full code. Can you see if I’m doing something wrong? Is this a bug?

struct Lab051: View {
    @State var session = ARKitSession()
    @State var worldTracking = WorldTrackingProvider()

    @State var worldAnchorEntities: [UUID: Entity] = [:]

    @State var placement = Entity()

    @State var subject : ModelEntity = {
        let subject = ModelEntity(
            mesh: .generateSphere(radius: 0.06),
            materials: [SimpleMaterial(color: .stepRed, isMetallic: false)])
        subject.setPosition([0, 0, 0], relativeTo: nil)

        let collision = CollisionComponent(shapes: [.generateSphere(radius: 0.06)])
        let input = InputTargetComponent()
        subject.components.set([collision, input])

        return subject
    }()

    var body: some View {
        RealityView { content in

            guard let scene = try? await Entity(named: "WorldTracking", in: realityKitContentBundle) else { return }
            content.add(scene)

            if let placementEntity = scene.findEntity(named: "PlacementPreview") {
                placement = placementEntity
            }

        } update: { content in
            for (_, entity) in worldAnchorEntities {
                if !content.entities.contains(entity) {
                    content.add(entity)
                }
            }
        }
        .modifier(DragGestureImproved())
        .gesture(tapGesture)
        .task {
            try! await setupAndRunWorldTracking()
        }
    }

    var tapGesture: some Gesture {
        TapGesture()
            .targetedToAnyEntity()
            .onEnded { value in

                if value.entity.name == "PlacementPreview" {
                    // If we tapped the placement preview cube, create an anchor
                    Task {
                        let anchor = WorldAnchor(originFromAnchorTransform: value.entity.transformMatrix(relativeTo: nil))
                        try await worldTracking.addAnchor(anchor)
                    }
                } else {
                    Task {
                        // Get the UUID we stored on the entity
                        let uuid = UUID(uuidString: value.entity.name) ?? UUID()

                        do {
                            try await worldTracking.removeAnchor(forID: uuid)
                        } catch {
                            print("Failed to remove world anchor \(uuid) with error: \(error).")
                        }

                    }
                }
            }
    }

    func setupAndRunWorldTracking() async throws {

        if WorldTrackingProvider.isSupported {
            do {
                try await session.run([worldTracking])

                for await update in worldTracking.anchorUpdates {
                    switch update.event {
                    case .added:

                        let subjectClone = subject.clone(recursive: true)
                        subjectClone.isEnabled = true
                        subjectClone.name = update.anchor.id.uuidString
                        subjectClone.transform = Transform(matrix: update.anchor.originFromAnchorTransform)

                        worldAnchorEntities[update.anchor.id] = subjectClone
                        print("🟢 Anchor added \(update.anchor.id)")

                    case .updated:

                        guard let entity = worldAnchorEntities[update.anchor.id] else {
                            print("No entity found to update for anchor \(update.anchor.id)")
                            return
                        }

                        entity.transform = Transform(matrix: update.anchor.originFromAnchorTransform)

                        print("🔵 Anchor updated \(update.anchor.id)")

                    case .removed:

                        worldAnchorEntities[update.anchor.id]?.removeFromParent()
                        worldAnchorEntities.removeValue(forKey: update.anchor.id)
                        print("🔴 Anchor removed \(update.anchor.id)")

                        if let remainingAnchors = await worldTracking.allAnchors {
                            print("Remaining Anchors: \(remainingAnchors.count)")
                        }
                    }
                }
            } catch {
                print("ARKit session error \(error)")
            }
        }
    }
}

I can confirm this same issue is also occurring on my device.

I am using visionOS Developer Beta 2.5, and can reproduce this issue by using Apple's Sample Code Building local experiences with room tracking.

If Apple could confirm that this is either;

  • A bug in the ARKit World Anchor persistence
  • or expected behaviour, that ARKit World Anchors must persist at least one anchor

that would be very helpful for us!

Can not remove final World Anchor
 
 
Q