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

Xcode

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.Storyboard

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

 

 

 

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                     Camera

  • 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                          Console

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.

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:  

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!)
…

TextNode

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!)
        }
    }

Shoe Size                  Shoe Size

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.

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!

 

Subscribe to our Blog
 

Jorge Mendoza
Jorge Mendoza
Jorge, a former Gorilla, is a Senior iOS Developer. He has over 10 years of experience in software development and has focused on iOS development for the past 6. Jorge received his Bachelor's in Computer Science from the University of Costa Rica (UCR).

Deliver off-the-chart results.

WordPress Video Lightbox Plugin