How to create and locate anchors using Azure Spatial Anchors in Swift

Azure Spatial Anchors allow you to share anchors in the world between different devices. It supports several different development environments. In this article, we'll dive into how to use the Azure Spatial Anchors SDK, in Swift, to:

  • Correctly set up and manage an Azure Spatial Anchors session.
  • Create and set properties on local anchors.
  • Upload them to the cloud.
  • Locate and delete cloud spatial anchors.

Prerequisites

To complete this guide, make sure you have:

Cross Platform

Initialize the session

The main entry point for the SDK is the class representing your session. Typically you'll declare a field in the class that manages your view and native AR session.

Learn more about the ASACloudSpatialAnchorSession class.

    var _cloudSession : ASACloudSpatialAnchorSession? = nil
    // In your view handler
    _cloudSession = ASACloudSpatialAnchorSession()

Set up authentication

To access the service, you need to provide an account key, access token, or Microsoft Entra auth token. You can also read more about this in the Authentication concept page.

Account Keys

Account Keys are a credential that allows your application to authenticate with the Azure Spatial Anchors service. The intended purpose of Account Keys is to help you get started quickly. Especially during the development phase of your application's integration with Azure Spatial Anchors. As such, you can use Account Keys by embedding them in your client applications during development. As you progress beyond development, it's highly recommended to move to an authentication mechanism that is production-level, supported by Access Tokens, or Microsoft Entra user authentication. To get an Account Key for development, visit your Azure Spatial Anchors account, and navigate to the "Keys" tab.

Learn more about the ASASessionConfiguration class.

    _cloudSession!.configuration.accountKey = "MyAccountKey"

Access Tokens

Access Tokens are a more robust method to authenticate with Azure Spatial Anchors. Especially as you prepare your application for a production deployment. The summary of this approach is to set up a back-end service that your client application can securely authenticate with. Your back-end service interfaces with AAD at runtime and with the Azure Spatial Anchors Secure Token Service to request an Access Token. This token is then delivered to the client application and used in the SDK to authenticate with Azure Spatial Anchors.

    _cloudSession!.configuration.accessToken = "MyAccessToken"

If an access token isn't set, you must handle the TokenRequired event, or implement the tokenRequired method on the delegate protocol.

You can handle the event synchronously by setting the property on the event arguments.

Learn more about the tokenRequired protocol method.

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        args.accessToken = "MyAccessToken"
    }

If you need to execute asynchronous work in your handler, you can defer setting the token by requesting a deferral object and then completing it, as in the following example.

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        let deferral = args.getDeferral()
        myGetTokenAsync( withCompletionHandler: { (myToken: String?) in
            if (myToken != nil) {
                args.accessToken = myToken
            }
            deferral?.complete()
        })
    }

Microsoft Entra authentication

Azure Spatial Anchors also allows applications to authenticate with user Microsoft Entra ID (Active Directory) tokens. For example, you can use Microsoft Entra tokens to integrate with Azure Spatial Anchors. If an Enterprise maintains users in Microsoft Entra ID, you can supply a user Microsoft Entra token in the Azure Spatial Anchors SDK. Doing so allows you to authenticate directly to the Azure Spatial Anchors service for an account that's part of the same Microsoft Entra tenant.

    _cloudSession!.configuration.authenticationToken = "MyAuthenticationToken"

Like with access tokens, if a Microsoft Entra token isn't set, you must handle the TokenRequired event, or implement the tokenRequired method on the delegate protocol.

You can handle the event synchronously by setting the property on the event arguments.

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        args.authenticationToken = "MyAuthenticationToken"
    }

If you need to execute asynchronous work in your handler, you can defer setting the token by requesting a deferral object and then completing it, as in the following example.

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        let deferral = args.getDeferral()
        myGetTokenAsync( withCompletionHandler: { (myToken: String?) in
            if (myToken != nil) {
                args.authenticationToken = myToken
            }
            deferral?.complete()
        })
    }

Setting up the library

Invoke Start() to enable your session to process environment data.

To handle events raised by your session, set the delegate property of your session to an object, like your view. This object must implement the SSCCloudSpatialAnchorSessionDelegate protocol.

Learn more about the start method.

    _cloudSession!.session = self.sceneView.session;
    _cloudSession!.delegate = self;
    _cloudSession!.start()

Provide frames to the session

The spatial anchor session works by mapping the space around the user. Doing so helps to determine where anchors are located. Mobile platforms (iOS & Android) require a native call to the camera feed to obtain frames from your platform's AR library. In contrast, HoloLens is constantly scanning the environment, so there's no need for a specific call like on mobile platforms.

Learn more about the processFrame method.

    _cloudSession?.processFrame(self.sceneView.session.currentFrame)

Provide feedback to the user

You can write code to handle the session updated event. This event fires every time the session improves its understanding of your surroundings. Doing so, allows you to:

  • Use the UserFeedback class to provide feedback to the user as the device moves and the session updates its environment understanding. To do this,
  • Determine at what point there's enough tracked spatial data to create spatial anchors. You determine this with either ReadyForCreateProgress or RecommendedForCreateProgress. Once ReadyForCreateProgress is above 1, we have enough data to save a cloud spatial anchor, though we recommend you wait until RecommendedForCreateProgress is above 1 to do so.

Learn more about the sessionUpdated protocol method.

    internal func sessionUpdated(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASASessionUpdatedEventArgs!) {
        let status = args.status!
        if (status.userFeedback.isEmpty) {
            return
        }
        _feedback = "Feedback: \(FeedbackToString(userFeedback:status.userFeedback)) - Recommend Create=\(status.recommendedForCreateProgress * 100)"
    }

Create a cloud spatial anchor

To create a cloud spatial anchor, you first create an anchor in your platform's AR system, and then create a cloud counterpart. You use the CreateAnchorAsync() method.

Learn more about the ASACloudSpatialAnchor class.

    // Create a local anchor, perhaps by hit-testing and creating an ARAnchor
    var localAnchor : ARAnchor? = nil
    let hits = self.sceneView.session.currentFrame?.hitTest(CGPoint(x:0.5, y:0.5), types: ARHitTestResult.ResultType.estimatedHorizontalPlane)
    if (hits!.count == 0) return
    // The hitTest method sorts the resulting list by increasing distance from the camera
    // The first hit result will usually be the most relevant when responding to user input
    localAnchor = ARAnchor(transform:hits![0].worldTransform)
    self.sceneView.session.add(anchor: _localAnchor!)

    // If the user is placing some application content in their environment,
    // you might show content at this anchor for a while, then save when
    // the user confirms placement.
    var cloudAnchor : ASACloudSpatialAnchor? = nil
    cloudAnchor = ASACloudSpatialAnchor()
    cloudAnchor!.localAnchor = localAnchor
    _cloudSession?.createAnchor(cloudAnchor!, withCompletionHandler: { (error: Error?) in
        if (error != nil) {
            _feedback = "Save Failed:\(error!.localizedDescription)"
            return
        }
        _feedback = "Created a cloud anchor with ID=\(cloudAnchor!.identifier!)"
    })

As described earlier, you need sufficient environment data captured before trying to create a new cloud spatial anchor. That means ReadyForCreateProgress has to be above 1, though we recommend you wait until RecommendedForCreateProgress is above 1 to do so.

Learn more about the getStatusWithCompletionHandler method.

    _cloudSession?.getStatusWithCompletionHandler( { (value:ASASessionStatus, error:Error?) in
        if (error != nil) {
            _feedback = "Session status error:\(error!.localizedDescription)"
            return
        }
        if (value!.recommendedForCreateProgress <> 1.0) {
            return
        }
        // Issue the creation request ...
    })

Set properties

You may choose to add some properties when saving your cloud spatial anchors. Like the type of object being saved, or basic properties like whether it should be enabled for interaction. Doing so can be useful upon discovery: you can immediately render the object for the user, for example a picture frame with blank content. Then, a different download in the background gets additional state details, for example, the picture to display in the frame.

Learn more about the appProperties property.

    var cloudAnchor : ASACloudSpatialAnchor? = nil
    cloudAnchor = ASACloudSpatialAnchor()
    cloudAnchor!.localAnchor = localAnchor
    cloudAnchor!.appProperties = [ "model-type" : "frame", "label" : "my latest picture" ]
    _cloudSession?.createAnchor(cloudAnchor!, withCompletionHandler: { (error: Error?) in
        // ...
    })

Update properties

To update the properties on an anchor, you use the UpdateAnchorProperties() method. If two or more devices try to update properties for the same anchor at the same time, we use an optimistic concurrency model. Which means that the first write will win. All other writes will get a "Concurrency" error: a refresh of the properties would be needed before trying again.

Learn more about the updateAnchorProperties method.

    var anchor : ASACloudSpatialAnchor? = /* locate your anchor */;
    anchor!.appProperties["last-user-access"] = "just now"
    _cloudSession?.updateAnchorProperties(anchor!, withCompletionHandler: { (error:Error?) in
        if (error != nil) {
            _feedback = "Updating Properties Failed:\(error!.localizedDescription)"
        }
    })

You can't update the location of an anchor once it has been created on the service - you must create a new anchor and delete the old one to track a new position.

If you don't need to locate an anchor to update its properties, you can use the GetAnchorPropertiesAsync() method, which returns a CloudSpatialAnchor object with properties.

Learn more about the getAnchorProperties method.

    _cloudSession?.getAnchorProperties("anchorId", withCompletionHandler: { (anchor:SCCCloudSpatialAnchor?, error:Error?) in
        if (error != nil) {
            _feedback = "Getting Properties Failed:\(error!.localizedDescription)"
        }
        if (anchor != nil) {
            anchor!.appProperties["last-user-access"] = "just now"
            _cloudSession?.updateAnchorProperties(anchor!, withCompletionHandler: { (error:Error?) in
                // ...
            })
        }
    })

Set expiration

It's also possible to configure your anchor to expire automatically at a given date in the future. When an anchor expires, it will no longer be located or updated. Expiration can only be set when the anchor is created, before saving it to the cloud. Updating expiration afterwards isn't possible. If no expiration is set during anchor creation, the anchor will only expire when deleted manually.

Learn more about the expiration property.

    let secondsInAWeek = 60.0 * 60.0 * 24.0 * 7.0
    let oneWeekFromNow = Date(timeIntervalSinceNow: secondsInAWeek)
    cloudAnchor!.expiration = oneWeekFromNow

Locate a cloud spatial anchor

Being able to locate a previously saved cloud spatial anchor is one of the main reasons for using Azure Spatial Anchors. For this, we're using "Watchers". You can only use one Watcher at a time; multiple Watchers are not supported. There are several different ways (also known as Anchor Locate Strategies) a Watcher can locate a cloud spatial anchor. You can use one strategy on a watcher at a time.

  • Locate anchors by identifier.
  • Locate anchors connected to a previously located anchor. You can learn about anchor relationships here.
  • Locate anchor using Coarse relocalization.

Note

Each time you locate an anchor, Azure Spatial Anchors will attempt to use the environment data collected to augment the visual information on the anchor. If you are having trouble locating an anchor, it can be useful to create an anchor, then locate it several times from different angles and lighting conditions.

If you're locating cloud spatial anchors by identifier, you can store the cloud spatial anchor identifier in your application's back-end service, and make it accessible to all devices that can properly authenticate to it. For an example of this, see Tutorial: Share Spatial Anchors across devices.

Instantiate an AnchorLocateCriteria object, set the identifiers you're looking for, and invoke the CreateWatcher method on the session by providing your AnchorLocateCriteria.

Learn more about the createWatcher method.

    let criteria = ASAAnchorLocateCriteria()!
    criteria.identifiers = [ "id1", "id2", "id3" ]
    _cloudSession!.createWatcher(criteria)

After your watcher is created, the AnchorLocated event will fire for every anchor requested. This event fires when an anchor is located, or if the anchor can't be located. If this situation happens, the reason will be stated in the status. After all anchors for a watcher are processed, found or not found, then the LocateAnchorsCompleted event will fire. There is a limit of 35 identifiers per watcher.

Learn more about the anchorLocated protocol method.

    internal func anchorLocated(_ cloudSession: ASACloudSpatialAnchorSession!, _ args: ASAAnchorLocatedEventArgs!) {
        let status = args.status
        switch (status) {
        case ASALocateAnchorStatus.located:
            let foundAnchor = args.anchor
            // Go add your anchor to the scene...
            break
        case ASALocateAnchorStatus.alreadyTracked:
            // This anchor has already been reported and is being tracked
            break
        case ASALocateAnchorStatus.notLocatedAnchorDoesNotExist:
            // The anchor was deleted or never existed in the first place
            // Drop it, or show UI to ask user to anchor the content anew
            break
        case ASALocateAnchorStatus.notLocated:
            // The anchor hasn't been found given the location data
            // The user might in the wrong location, or maybe more data will help
            // Show UI to tell user to keep looking around
            break
        }
    }

Delete anchors

Deleting anchors when no longer used is a good practice to include early on in your development process and practices, to keep your Azure resources cleaned up.

Learn more about the delete method.

    _cloudSession?.delete(cloudAnchor!, withCompletionHandler: { (error: Error?) in
        // Perform any processing you may want when delete finishes
    })

Pause, reset, or stop the session

To stop the session temporarily, you can invoke Stop(). Doing so will stop any watchers and environment processing, even if you invoke ProcessFrame(). You can then invoke Start() to resume processing. When resuming, environment data already captured in the session is maintained.

Learn more about the stop method.

    _cloudSession!.stop()

To reset the environment data that has been captured in your session, you can invoke Reset().

Learn more about the reset method.

    _cloudSession!.reset()

To clean up properly after a session release all references.

    _cloudSession = nil

Next steps

In this guide, you learned about how to create and locate anchors using the Azure Spatial Anchors SDK. To learn more about anchor relationships, continue to the next guide.