Blog

ARKit Developer Tutorial: How To Build a Measuring App with Apple’s Augmented Reality SDK

In this post, we are going to discuss the basic settings, commands and tools for Apple’s new augmented reality SDK, “ARKit,” available for iOS 11. In order to explain all the steps to build a basic app and use its functionality in ARKit, we will be creating a “shoe measuring app” that will measure the length of a shoe and get its size. But first, let’s talk a little bit about ARKit.

ARKit is a new framework that allows you to easily implement augmented reality experiences on the iPhone and iPad. If you are not already familiar with augmented reality, it is essentially a blending of virtual elements with the real world that allows one to “place” 2D or 3D objects in the real world using the device’s camera.

ARKit includes features that make it easy to implement, such as World Tracking (which tracks the objects in the scene), Light Estimation (used to place realistic shadows on virtual objects), and Surface Detection (for realistic object placement). There are a few different rendering options for your virtual objects, including SceneKit (ARSCNView), SpriteKit (ARSKView), and Metal. Metal is a bit more involved and more suitable if you want to build your own rendering engine (or integrate it with a third-party engine). For the simple needs of our project, though, we decided to use SceneKit.

Much inspiration for this post can be found here. You can also visit Apple’s ARKit developer site for more information.

Let’s dive into the project.

ARKit Shoe Measuring App

Creating an ARKit Project

Open Xcode and create a new project using the “Augmented Reality App” template.

Xcode

Project name: ARkitApp.

Project name: ARkitApp.

Language: Swift.

Content Technology: SceneKit (for its 3D and 2D features)

Navigator

Note that in the Navigator Area, there is a New File Group called “art.scnassets.”

Here we found a ship.scn file and the texture.png file; in this group one can add all the 3D assets and the textures for the objects in SceneKit.

*For our example, please delete all those files along with the code in the ViewController.swift file

NSCameraUsageDescription in Info.plist

Selecting the Augmented Reality Template in the new project wizard will add NSCameraUsageDescription to the targets Info.plist. Since augmented reality requires the use of a camera, you need to add a usage description that will be displayed to the user before the app is granted use of the camera.

Augmented Reality Template

MeasureShoeViewController.swift

Create a new Swift file called “MeasureShoeViewController” and clean out all the objects in the main.storyboard.

In the MeasureShoeViewController file, add the following imports along with the ARSCNViewDelegate.  

import UIKit import SceneKit import ARKit class MeasureShoeViewController: UIViewController, ARSCNViewDelegate { }

*ARSCNViewDelegate: Provide SceneKit content corresponding to ARAnchor objects tracked by the view’s AR session, or to manage the view’s automatic updating of such content. This protocol extends the ARSessionObserver protocol, so your session delegate can also implement those methods to respond to changes in session status.

Adding the scene view to the storyboard.

1. In the main.storyboard, add a new ViewController.ARKit SceneKit View

2. Set the custom class for MeasureShoeViewController to the ViewController, add the ARKit SceneView object to the view controller.

ARKit Storyboard

Setting up the SceneView

     @IBOutlet var scnView: ARSCNView!    func setupScene()  {        let scene = SCNScene()        self.scnView.delegate = self        self.scnView.showsStatistics = true        self.scnView.automaticallyUpdatesLighting = true        self.scnView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]        self.scnView.scene = scene    }

1. Add the IBOutlet to the SceneView object in the storyboard file.

2. Create a setup scene function and set up the ARSCNViewDelegate.

3. Call the setupScene function from viewDidLoad()

  • Please notice the debug options. This will help us understand how the camera detects surfaces and the objects in world tracking.

Setting up the ARSession

1. Add a setup function in which you initialize an ARSession. Next, create a world-tracking session with horizontal plane detection, set the configuration to the scnView variable, and then call that function from viewWillAppear.

   func setupARSession() {        let configuration = ARWorldTrackingConfiguration()        configuration.planeDetection = .horizontal                scnView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])    }            override func viewWillDisappear(_ animated: Bool) {        super.viewWillDisappear(animated)        scnView.session.pause()    }

  2. Run the project.

Note that in the camera there are yellow points; those points represent the surface and the objects in the image (in this case, world tracking).

  • That is the origin point.  

Camera Origin Point                     AR Origin Point

  • It is also important to note that the camera and the session take some time to get ready. The delegate function func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) could help us inform the user when the app is ready.

3. Implement the ARSCNViewDelegate function: session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera). To check the states, we can print the camera state to the console.

@IBOutlet weak var infoLabel: UILabel! func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {        print("Camera State: \(camera.trackingState)") }

Creating a Sphere

1. Create a new file called: SCNSphere+Init.swift

1.1. Create a new extension of SCNSphere.

1.2. Add an extension to SCNSphere to include a convenience initializer for setting color and radius.

import UIKit import SceneKit extension SCNSphere {    convenience init(color: UIColor, radius: CGFloat) {        self.init(radius: radius)                let material = SCNMaterial()        material.diffuse.contents = color        materials = [material]    } }

We will use this new initializer to create a sphere node of a specific color and radius further down in this post.

Measuring the Distance between Two Points

1. Add a Tap Gesture to the SceneView to take the taps into account to start and finish the measuring. 

2. Add an extension to SCNVector3 for calculating the distance between 2 vectors.

 // Code Example from https://github.com/kravik/ArMeasureDemo/blob/master/ArMeasureDemo/ViewController.swift extension SCNVector3 {    func distance(to destination: SCNVector3) -> CGFloat {        let dx = destination.x - x        let dy = destination.y - y        let dz = destination.z - z        return CGFloat(sqrt(dx*dx + dy*dy + dz*dz))    }        static func positionFrom(matrix: matrix_float4x4) -> SCNVector3 {        let column = matrix.columns.3        return SCNVector3(column.x, column.y, column.z)    }

3. Create a new array of nodes in the MeasureShoeViewController class.

3.1. Add the code to place a sphere in the Scene View when the user taps on the Scene view

3.2. The hitTestResults variable contains the right position in the 3D vector using the world-tracking feature; in this way, we create an object and place it in the right position in the SceneView (by adding a child node).

var nodes: [SCNNode] = [] // MARK: Gesture handlers    @objc func handleTap(sender: UITapGestureRecognizer) {                let tapLocation = self.scnView.center// Get the center point, of the SceneView.        let hitTestResults = scnView.hitTest(tapLocation, types:.featurePoint)                if let result = hitTestResults.first {            let position = SCNVector3.positionFrom(matrix: result.worldTransform)            let sphere = SCNSphere(color: self.nodeColor, radius: self.nodeRadius)            let node = SCNNode(geometry: sphere)            scnView.scene.rootNode.addChildNode(node)            nodes.append(node)        }    }

4. Run the app and check to see how the spheres were added to the sceneView.

5. Clean the nodes in the SceneView by creating a function to remove all the nodes in the sceneView.

func cleanAllNodes() {        if nodes.count > 0 {            for node in nodes {                node.removeFromParentNode()            }            nodes = []        }    }

6. Calculate the distance between two points and draw a line.

6.1. Add the extension to the SCNNode with the functions:

  • static func createLineNode(fromNode: SCNNode, toNode: SCNNode) -> SCNNode
  • static func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry

extension SCNNode {    static func createLineNode(fromNode: SCNNode, toNode: SCNNode, andColor color: UIColor) -> SCNNode {        let line = lineFrom(vector: fromNode.position, toVector: toNode.position)        let lineNode = SCNNode(geometry: line)        let planeMaterial = SCNMaterial()        planeMaterial.diffuse.contents = color        line.materials = [planeMaterial]        return lineNode    }        static func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {        let indices: [Int32] = [0, 1]        let source = SCNGeometrySource(vertices: [vector1, vector2])        let element = SCNGeometryElement(indices: indices, primitiveType: .line)        return SCNGeometry(sources: [source], elements: [element])    } }

6.2.  Add the code to calculate the distance in the tapHandler function.  

@objc func handleTap(sender: UITapGestureRecognizer) {                let tapLocation = self.scnView.center // Get the center point, of the SceneView.        let hitTestResults = scnView.hitTest(tapLocation, types:.featurePoint)                if let result = hitTestResults.first {                         if nodes.count == 2 {                cleanAllNodes()            }                        let position = SCNVector3.positionFrom(matrix: result.worldTransform)            let sphere = SCNSphere(color: self.nodeColor, radius: self.nodeRadius)            let node = SCNNode(geometry: sphere)                        node.position = position                        scnView.scene.rootNode.addChildNode(node)                        // Get the Last Node from the list            let lastNode = nodes.last                        // Add the Sphere to the list.            nodes.append(node)                        // Setting our starting point for drawing a line in real time            self.startNode = nodes.last                        if lastNode != nil {                // If there is 2 nodes or more                if nodes.count >= 2 {                    // Create a node line between the nodes                    let measureLine = LineNode(from: (lastNode?.position)!,                                               to: node.position, lineColor: self.nodeColor)                    measureLine.name = "measureLine"                    // Add the Node to the scene.                    scnView.scene.rootNode.addChildNode(measureLine)                }                                self.distance = Double(lastNode!.position.distance(to: node.position)) * 100                print( String(format: "Distance between nodes:  %.2f cm", self.distance))                presentShoeSizes(distance: self.distance)            }        }    }

6.3. Add the code to delete the measureLines in the scene.

     func cleanAllNodes() {        if nodes.count > 0 {            for node in nodes {                node.removeFromParentNode()            }            for node in scnView.scene.rootNode.childNodes {                if node.name == "measureLine" {                    node.removeFromParentNode()                }            }            nodes = []        }    }

6.4 Run the project and check the console.  

Console                          Distance between nodes

6.5. Add the UI to show the distance and an image to recognize the center of the screen.

6.5.1. Add an image with a target or bullseye and set it in the middle of the view.

AR Target

Presenting a TextNode

The distance will be presented as a node in the SceneView; for that, we are going to create a new SCNNode class.

1. Create a new Swift file called TextNode.swift.

2. Create the TextNode class that inherits from the SCNNode class.

import Foundation import SceneKit class TextNode: SCNNode {    private let extrusionDepth: CGFloat = 0.01                  // Text depth    private let textNodeScale = SCNVector3Make(0.2, 0.2, 0.2)   // Scale applied to node    private var text: SCNText?        public var color = UIColor.black {        didSet {            text?.firstMaterial?.diffuse.contents = color        }    }        public var font: UIFont? = UIFont.systemFont(ofSize: 0.1) {        didSet {            text?.font = UIFont(name: "AvenirNext-Bold", size: 0.1)        }    }        public var alignmentMode = kCAAlignmentCenter {        didSet {            text?.alignmentMode = kCAAlignmentCenter        }    }        init(_ string: String) {        super.init()        let constraints = SCNBillboardConstraint()        let node = SCNNode()        let max, min: SCNVector3        let tx, ty, tz: Float                constraint.freeAxes = .Y                text = SCNText(string: string, extrusionDepth: extrusionDepth)        text?.alignmentMode = alignmentMode        text?.firstMaterial?.diffuse.contents = color        text?.firstMaterial?.specular.contents = UIColor.white        text?.firstMaterial?.isDoubleSided = true        text?.chamferRadius = extrusionDepth        text?.font = font                max = text!.boundingBox.max        min = text!.boundingBox.min                tx = (max.x - min.x) / 2.0        ty = min.y        tz = Float(extrusionDepth) / 2.0                node.geometry = text        node.scale = scale        node.pivot = SCNMatrix4MakeTranslation(tx, ty, tz)                self.addChildNode(node)                self.constraints = constraints    }        required init?(coder aDecoder: NSCoder) {        fatalError("init(coder:) has not been implemented")    } }

3. Adding the TextNode to the SceneView.

3.1. We are going to create a new function in the MeasureShoeViewController class called: func presentShoeSizes(distance: Double). The main thing here is to calculate the position in which to put the text node; in this case, between the two sphereNodes.

func presentShoeSizes(distance: Double) {                let shoeSize = shoeHelper.shoeSizeFromCm(sizeInCm: distance,                                                 region: self.selectedRegion!,                                                 shoeType: self.selectedShoeType!)                if nodes.count == 2 {            // Get the Last Node from the list            let lastNode = nodes.last            let firtsNode = nodes.first                        let formatter = NumberFormatter()            formatter.minimumFractionDigits = 0            formatter.maximumFractionDigits = 1                        let stringSize = formatter.string(for: shoeSize)                        if let node1 = firtsNode, let node2 = lastNode  {                // Calculate the middle point between the two SphereNodes.                let minPosition = node1.position                let maxPosition = node2.position                let dx = ((maxPosition.x + minPosition.x)/2.0)                let dy = (maxPosition.y + minPosition.y)/2.0 + 0.04                let dz = (maxPosition.z + minPosition.z)/2.0                let position =  SCNVector3(dx, dy, dz)                // Create the textNode                self.textNode = TextNode(stringSize!)                self.textNode?.color = nodeColor                self.textNode?.position = position                self.textNode?.font = UIFont(name: "AvenirNext-Bold", size: 0.1)                self.scnView.scene.rootNode.addChildNode(self.textNode!)            }        }    }

3.2. Call the presentShoeSizes function after getting two nodes in the func handleTap(sender: UITapGestureRecognizer)

3.2.1. At this moment, we are not showing the shoe size based on the distance, but we will do that soon.

Getting the Shoe Size

There are several ways to calculate the size of a shoe, such as a formula or a comparative table. The problem here is that formulas don’t always return an accurate measurement. To find a relatively accurate formula, this Wikipedia article could help you: https://en.wikipedia.org/wiki/Shoe_size. In this example, I created a plist file with all the sizes for men, women, and kids. This is an example of the file:  

ARKit App File

Based on this file, we were able to get the sizes and code simple conditions in order to get the shoe sizes given the distance of the shoes.

1. Create a new file called: ShoeSizeCalculatorHelper.swift and set two enums to get the region and type of shoe.

enum ShoeSizeRegion: Int {    case europe = 0    case us = 1    case uk = 2    static var count: Int { return ShoeSizeRegion.uk.hashValue + 1 }        var description: String {        switch self {        case .europe: return "EU"        case .us   : return "US"        case .uk  : return "UK"        }    } } enum ShoeType: Int {    case men = 0    case women = 1    case kids = 2    static var count: Int { return ShoeType.kids.hashValue + 1 }        var description: String {        switch self {        case .men: return "Men"        case .women   : return "Women"        case .kids  : return "Kids"        }    } }

2. Now create the ShoeSizeCalculatorHelper class. In the Init, load the array of sizes and filter the sizes for men, women, and kids. The arrays also need to be sorted.

class ShoeSizeCalculatorHelper {        let inchToCm: Double = 2.54    var menSizes: Array?    var womenSizes: Array?    var kidsSizes: Array?        init() {                guard let path = Bundle.main.path(forResource:"ShoeSizeChart", ofType: "plist"),            let results = NSDictionary(contentsOfFile: path)  else {                return        }                if let shoeSizesArray = results.value(forKey: "ShoeSizesArray") as? Array {            self.menSizes = shoeSizesArray.filter {$0.value(forKey: "Female") as! Int == 0 && $0.value(forKey: "Children") as! Int == 0 }            print(menSizes ?? "Problems Loading Men Sizes")                        self.womenSizes = shoeSizesArray.filter{$0.value(forKey: "Female") as! Int == 1 && $0.value(forKey: "Children") as! Int == 0 }            print(womenSizes ?? "Problems Loading Women Sizes")                        self.kidsSizes = shoeSizesArray.filter{$0.value(forKey: "Children") as! Int == 1 }            print(kidsSizes ?? "Problems Loading Kids Sizes")                        sortArrays()        }    }        func sortArrays() {        self.menSizes?.sort { ($0.value(forKey: "CM") as! NSNumber).compare($1.value(forKey: "CM") as! NSNumber) == .orderedAscending }        self.womenSizes?.sort { ($0.value(forKey: "CM") as! NSNumber).compare($1.value(forKey: "CM") as! NSNumber) == .orderedAscending }        self.kidsSizes?.sort { ($0.value(forKey: "CM") as! NSNumber).compare($1.value(forKey: "CM") as! NSNumber) == .orderedAscending }            }

3. Now create a function to get the size depending on the region and the genre category.

func shoeSizeFromCm (sizeInCm: Double, region: ShoeSizeRegion, shoeType: ShoeType) -> Double {        return calculateShoeSizeFromCatalog(sizeInCm:sizeInCm, region: region, shoeType: shoeType)    }    func calculateShoeSizeFromCatalog(sizeInCm: Double, region: ShoeSizeRegion, shoeType: ShoeType) ->Double {        var shoeSize: Double = 0        var regionTag: String                switch region {        case .europe:            regionTag = "Euro"        case .uk:            regionTag = "UK"        case .us:            regionTag = "US"        }              switch shoeType {        case .men:            shoeSize = calculateSize(withArray: self.menSizes, sizeInCm: sizeInCm, regionTag: regionTag)        case .women:             shoeSize = calculateSize(withArray: self.womenSizes, sizeInCm: sizeInCm, regionTag: regionTag)        case .kids:            shoeSize = calculateSize(withArray: self.kidsSizes, sizeInCm: sizeInCm, regionTag: regionTag)        }        return shoeSize    }func calculateSize(withArray sizesArray: Array?, sizeInCm: Double, regionTag: String) -> Double {        var shoeSize: Double = 0        if let sizesArray = sizesArray {            for size in sizesArray {                if sizeInCm >= (size.value(forKey: "CM") as! NSNumber).doubleValue {                    shoeSize = (size.value(forKey: regionTag) as! NSNumber).doubleValue                }            }        }        return shoeSize    }

4. Add two variables to the MeasureShoeViewController class to hold the region and the shoe genre and initialize in the ViewDidLoad to .men and .us.

var selectedShoeType: ShoeType? var selectedRegion: ShoeSizeRegion?

5. Call the function to determine the size of the shoe given the distance, the region and the genre, store the result in the let and set the text to our TextNode with the calculation of the position between the nodes.

  func presentShoeSizes(distance: Double) {                let shoeSize = shoeHelper.shoeSizeFromCm(sizeInCm: distance, region: self.selectedRegion!, shoeType: self.selectedShoeType! )              if nodes.count == 2 {            …                        let stringSize = formatter.string(for: shoeSize)            …                        // Create the textNode                self.textNode = TextNode(text: stringSize!, position: position, textColor: self.nodeColor)                self.scnView.scene.rootNode.addChildNode(self.textNode!) …

ARKit TextNode shoe

How to create a NodeLine and draw it in real time?

As we did with the TextNode and the SphereNode, a line requires the same process: create a geometry and a material and put them in a SCNNode. The only trick here is calculate the position starting in a SphereNode and finishing in another.

1. Create a new Swift file called: LineNode and set the class as LineNode: SCNNode.

2. Create a new Init function with two SCNVector3 parameters (start position and final position), along with the color of the line.

class LineNode: SCNNode {        init(from vectorA: SCNVector3, to vectorB: SCNVector3, lineColor color: UIColor) {        super.init()                let height = self.distance(from: vectorA, to: vectorB)                self.position = vectorA        let nodeVector2 = SCNNode()        nodeVector2.position = vectorB                let nodeZAlign = SCNNode()        nodeZAlign.eulerAngles.x = Float.pi/2                let box = SCNBox(width: 0.003, height: height, length: 0.001, chamferRadius: 0)        let material = SCNMaterial()        material.diffuse.contents = color        box.materials = [material]                let nodeLine = SCNNode(geometry: box)        nodeLine.position.y = Float(-height/2) + 0.001        nodeZAlign.addChildNode(nodeLine)                self.addChildNode(nodeZAlign)                self.constraints = [SCNLookAtConstraint(target: nodeVector2)]    }        required init?(coder aDecoder: NSCoder) {        super.init(coder: aDecoder)    }        func distance(from vectorA: SCNVector3, to vectorB: SCNVector3)-> CGFloat {        return CGFloat (sqrt(            (vectorA.x - vectorB.x) * (vectorA.x - vectorB.x)                +   (vectorA.y - vectorB.y) * (vectorA.y - vectorB.y)                +   (vectorA.z - vectorB.z) * (vectorA.z - vectorB.z)))    }     }

3. Replace the function to createLine in the handleTap(sender: UITapGestureRecognizer) function.

 // Create a node line between the nodes let measureLine = LineNode(from: (lastNode?.position)!, to: sphere.position, lineColor: self.nodeColor)

4. We only need two SphereNodes at the same time to measure the distance between two points. For this reason, add this condition after getting the hitTestResult.first.

  if nodes.count == 2 {      cleanAllNodes() }

5. To draw a line in real time, we will need the Delegate function: func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval).

6. We also need a function to get the center position and return.  

func doHitTestOnExistingPlanes() -> SCNVector3? {        // hit-test of view's center with existing-planes        let results = scnView.hitTest(view.center, types: .featurePoint)        // check if result is available        if let result = results.first {            // get vector from transform            let hitPos = SCNVector3.positionFrom(matrix: result.worldTransform)            return hitPos        }        return nil    }

7. In the Delegate function, add a LineNode from the start node to the current position. That will draw the line. The second time the function is called, we need to delete the line and redraw it.   

    // renderer callback method    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {                if nodes.count == 2 {            self.startNode =  nil            self.lineNode?.removeFromParentNode()        }                DispatchQueue.main.async {            // get current hit position            // and check if start-node is available            guard let currentPosition = self.doHitTestOnExistingPlanes(),                let start = self.startNode else {                    return            }            // line-node            self.lineNode?.removeFromParentNode()            self.lineNode = LineNode(from: start.position, to: currentPosition, lineColor: self.nodeColor)            self.lineNode?.name = "lineInRealTime"            self.scnView.scene.rootNode.addChildNode(self.lineNode!)        }    }

ARKit NodeLine Shoe Size                  ARKit NodeLine Shoe Size full length

Final Details: Changing the Size Region and the Shoe Type (Men, Women, Kids)

For the final touches in our app, we need to create an interface for selecting/changing the shoe-measuring settings. For this, we are going to implement a simple Present a ViewController and return the information using a Delegate function.

1. Add a view with a label to represent the selection in the MeasureShoeViewController in the storyboard file

2. Add a new ViewController to the storyboard with navigation and set two UIPickerView for selecting the options.

ARKit ViewController

3. Create a new Swift file for our new ViewController: ShoeCategoriesViewController and set the class with the UIPickerViewDelegates to return the selection to MeasureShoeViewController to create your own Delegate function.

4. To populate the UIPickers, you could use the enums ShoeSizeRegion and ShoeType. The code below explains how to do that.

5. Set the SelectedShoeType and seletedRegion at the func prepare(for segue: UIStoryboardSegue, sender: Any?) in MeasureShoeViewController.

6. Finally, implement the Delegate function: dismissViewController.

7. Don’t forget to include all the nodes in the cleanAllNodes function: the LineNode, the TextNode, Nodes Array (SphereNodes) and the startNode.

protocol ShoeCategoriesViewControllerDelegate: class {    func dismissViewController(region:ShoeSizeRegion,  type: ShoeType) } class ShoeCategoriesViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {    @IBOutlet weak var regionPicker: UIPickerView!    @IBOutlet weak var shoeTypePicker: UIPickerView!    var selectedShoeType: ShoeType?    var selectedRegion: ShoeSizeRegion?    weak var delegate: ShoeCategoriesViewControllerDelegate?        override func viewDidLoad() {        super.viewDidLoad()                self.regionPicker.delegate = self        self.shoeTypePicker.delegate = self    }        override func viewDidAppear(_ animated: Bool) {        super.viewDidAppear(animated)              DispatchQueue.main.async {            self.regionPicker.selectRow((self.selectedRegion?.hashValue)!, inComponent: 0, animated: true)            self.shoeTypePicker.selectRow((self.selectedShoeType?.hashValue)!, inComponent: 0, animated: true)        }            }        // MARK: UIPickerViewDataSource Delegate    func numberOfComponents(in pickerView: UIPickerView) -> Int {        return 1    }        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {        if pickerView == regionPicker {            return ShoeSizeRegion.count.hashValue        } else if pickerView == shoeTypePicker {            return ShoeType.count.hashValue        } else {            return 0        }    }        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {                if pickerView == regionPicker {            return ShoeSizeRegion(rawValue: row)?.description        } else if pickerView == shoeTypePicker {            return ShoeType(rawValue: row)?.description        } else {            return ""        }    }        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {        if pickerView == regionPicker {            self.selectedRegion = ShoeSizeRegion(rawValue: row)        } else if pickerView == shoeTypePicker {            self.selectedShoeType = ShoeType(rawValue: row)        }    }        //MARK: IB Actions    @IBAction func DismissViewControllerAction(_ sender: Any) {        if let delegate = self.delegate {            delegate.dismissViewController(region: self.selectedRegion!, type: self.selectedShoeType!)            self.navigationController?.dismiss(animated: true, completion: nil)        }    } }

Conclusions

ARKit is a great option for creating an Augmented Reality application. The integration of SceneKit, SpriteKit and Metal makes it easy and simple. You could also save a lot of money by implementing your own solutions rather than buying a third-party solution.

While there is a lot to like about ARKit, there are some limitations. The world-tracking function is not always reliable, and sometimes ARKit loses the information of the surface or the objects it is tracking. This requires the user to constantly move the camera to detect the objects again. This is a problem when you are trying to measure something precisely. The other issue with ARKit is the number of devices it’s compatible with, since ARKit only runs on devices with processors A9 or greater. Even with its shortcomings, though, ARKit is a great 1.0 that will enable a whole new world of applications.

Interested in more “How To” posts? Check out our IoT Success Series!

Ready to be Unstoppable? Partner with Gorilla Logic, and you can be.

TALK TO OUR SALES TEAM