Storing the World Map

As of iOS 12, we can get the world map the user has assembled and store it for later use. For that to work, we need somewhere to store the world map. Open FileManagerExtension and add the following method:

ARKit/ScavengerHunt/FileManagerExtension.swift
 static​ ​func​ ​mapDataURL​() -> ​URL​ {
 return​ ​documentsURL​().​appendingPathComponent​(​"mapData"​)
 }

Next, we need to add a button to the user interface that triggers storing the world map. Open Main.storyboard and drag a button to the lower-right corner of the ARSKView. Pin the button to the bottom and left edge of the super view with a margin of 10 points.

Open the attribute inspector, change the title of the button to Done, and set the background color to white.

The button looks a bit strange right now—there’s no padding around its title. Open the size inspector with the shortcut 6 and change the content inset of the button such that the user interface looks like the following image. I used an inset of 10 points for left and right and an inset of 5 for top and bottom.

images/ARKit/done_button.png

Open the assistant editor. Press and hold the control and drag a connection from the button to right above the closing curly braces of ARViewController. Type in the name done, change the type to UIButton, and click Connect.

Open ARViewController.swift and import the LogStore logging library below the existing import statements:

ARKit/ScavengerHunt/ARViewController.swift
 import​ ​UIKit
 import​ ​SpriteKit
 import​ ​ARKit
 import​ ​LogStore

Then change the done(_:) method so that it looks like this:

ARKit/ScavengerHunt/ARViewController.swift
 @IBAction​ ​func​ ​done​(_ sender: ​UIButton​) {
  sceneView.session.getCurrentWorldMap { [​weak​ ​self​] worldMap, error ​in
 
 guard​ ​let​ worldMap = worldMap ​else​ {
 // ToDo: Present an alert about the error.
 printLog​(​"error: ​​\(​​String​(describing: error)​)​​"​)
 return
  }
 
 do​ {
 let​ data =
 try​ ​NSKeyedArchiver​.​archivedData​(withRootObject: worldMap,
  requiringSecureCoding: ​false​)
 try​ data.​write​(to: ​FileManager​.​mapDataURL​())
 self​?.​dismiss​(animated: ​true​, completion: ​nil​)
  } ​catch​ {
 printLog​(​"error: ​​\(​error​)​​"​)
  }
  }
 }

To get the world map, we call the method getCurrentWorldMap(completionHandler:) on the instance of ARSession. Generating the world map can take some time. When it’s ready, the completion handler (the closure we provide in the method call) is called.

We check if the world map is not nil. In this case we generate an instance of Data using NSKeyedArchiver. This is possible because ARWorldMap conforms to the protocol NSSecureCoding. When the data object is generated, we write it to the documents directory and dismiss the AR view controller.

Our app works best when we make sure the world mapping state is mapped before we get the current world map. Let’s disable the Done button as long as the mapping state is not mapped.

Open Main.storyboard, select the ARViewConroller in the overview, and activate the assistant editor if it’s not still open. Press and hold the control key and drag a connection from the Done button to right below the existing properties. Type in the name doneButton and click Connect. Xcode adds the following code:

ARKit/ScavengerHunt/ARViewController.swift
 @IBOutlet​ ​var​ doneButton: ​UIButton​!

Next add the highlighted line to the body of session(_:didUpdate:):

ARKit/ScavengerHunt/ARViewController.swift
 func​ ​session​(_ session: ​ARSession​, didUpdate frame: ​ARFrame​) {
 
» doneButton.isEnabled = frame.worldMappingStatus == .mapped
 
 switch​ frame.worldMappingStatus {
 case​ .limited:
  statusLabel.text = ​"Limited"
 case​ .extending:
  statusLabel.text = ​"Extending"
 case​ .mapped:
  statusLabel.text = ​"Mapped"
 default​:
  statusLabel.text = ​"Not available"
  }
 }

When this method gets called by the augmented reality session, we enable the button only if the mapping status is mapped. Build and run the app on your iPhone and verify that the Done button works as expected. If it doesn’t work, confirm that the button is connected to the code and that creating the world map doesn’t result in an error.

In the next section, we’ll display the AR view when the user approaches the stored location.