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:
| 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.
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:
| import UIKit |
| import SpriteKit |
| import ARKit |
| import LogStore |
Then change the done(_:) method so that it looks like this:
| @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:
| @IBOutlet var doneButton: UIButton! |
Next add the highlighted line to the body of session(_:didUpdate:):
| 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.