AR Persistence Model Using Swift 4.2
In the Previous Blog, we have discussed how to draw the AR Map in the real-world & save this model for later use.
Previous Blogs Link: https://mobikul.com/ar-persistence-model-using-swift-4-2-part-1/
Now we have to load save the map on real-world for this we have to save the file by giving some unique name, in the previous blog we have taken the var mapID: String = “”
by using this we can differentiate AR Model.
For this Follow this Steps:
1: Create A ViewController Class as “SuggestionARPathController”
2: Add SuggestionARPathController controller in StoryBoard.
3: Now Write the Code in this Controller.
let mapID is “1” and you have to load this map on the real world.
a: Create an Outlet.
1 2 3 4 5 6 |
class SuggestionARPathController: UIViewController { @IBOutlet weak var sceneView: ARSCNView! @IBOutlet var mapIndicator: UIButton! var mapID:String = "1" } |
b: Write the function for access URL of saving AR model.
1 2 3 4 5 6 7 8 9 |
func worldMapURL()->URL{ do { let id = mapID return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent("worldMapURL"+id) } catch { fatalError("Error getting world map URL from document directory.") } } |
c: Write the function for reset tracking Function
1 2 3 4 5 6 7 8 9 10 11 12 |
func resetTrackingConfiguration(with worldMap: ARWorldMap? = nil) { let configuration = ARWorldTrackingConfiguration() configuration.planeDetection = [.horizontal] let options: ARSession.RunOptions = [.resetTracking, .removeExistingAnchors] if let worldMap = worldMap { configuration.initialWorldMap = worldMap } sceneView.debugOptions = [.showFeaturePoints] sceneView.session.run(configuration, options: options) } |
d: Write the function to load the AR in the real world.
1 2 3 4 5 |
func loadSaveMap(){ guard let worldMapData = retrieveWorldMapData(from: worldMapURL()), let worldMap = unarchive(worldMapData: worldMapData) else { return } resetTrackingConfiguration(with: worldMap) } |
4: Set Up the Image/GIF that you have to draw over Real-world.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
extension SuggestionARPathController{ func displayProductDetailView()->SCNNode { let webViewPlane = SCNPlane(width: 0.15, height: 0.15) webViewPlane.cornerRadius = 0 let webViewNode = SCNNode(geometry: webViewPlane) // Set the web view as webViewPlane's primary texture webViewNode.geometry?.firstMaterial?.diffuse.contents = self.returnAnimationArrow() webViewNode.position.z -= 0.0 // position of images webViewNode.opacity = 0 webViewNode.eulerAngles.x = -.pi / 4 webViewNode.runAction(.sequence([ .wait(duration: 1.0), .fadeOpacity(to: 1.0, duration: 1.0), .moveBy(x: 0, y: 0, z: -0.00, duration: 1.0), .moveBy(x: 0, y: 0, z: -0.00, duration: 0.2) ]) ) return webViewNode } func returnAnimationArrow()->UIImageView{ let jeremyGif = UIImage.gifImageWithName("circle") let imageView = UIImageView(image: jeremyGif) imageView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) return imageView } } |
Note: Please add this extension in your Helper Swift file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
extension UIImage { public class func gifImageWithData(_ data: Data) -> UIImage? { guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { print("image doesn't exist") return nil } return UIImage.animatedImageWithSource(source) } public class func gifImageWithURL(_ gifUrl:String) -> UIImage? { guard let bundleURL:URL? = URL(string: gifUrl) else { print("image named \"\(gifUrl)\" doesn't exist") return nil } guard let imageData = try? Data(contentsOf: bundleURL!) else { print("image named \"\(gifUrl)\" into NSData") return nil } return gifImageWithData(imageData) } public class func gifImageWithName(_ name: String) -> UIImage? { guard let bundleURL = Bundle.main .url(forResource: name, withExtension: "gif") else { print("SwiftGif: This image named \"\(name)\" does not exist") return nil } guard let imageData = try? Data(contentsOf: bundleURL) else { print("SwiftGif: Cannot turn image named \"\(name)\" into NSData") return nil } return gifImageWithData(imageData) } class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double { var delay = 0.1 let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) let gifProperties: CFDictionary = unsafeBitCast( CFDictionaryGetValue(cfProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()), to: CFDictionary.self) var delayObject: AnyObject = unsafeBitCast( CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()), to: AnyObject.self) if delayObject.doubleValue == 0 { delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self) } delay = delayObject as! Double if delay < 0.1 { delay = 0.1 } return delay } class func gcdForPair(_ a: Int?, _ b: Int?) -> Int { var a = a var b = b if b == nil || a == nil { if b != nil { return b! } else if a != nil { return a! } else { return 0 } } if a < b { let c = a a = b b = c } var rest: Int while true { rest = a! % b! if rest == 0 { return b! } else { a = b b = rest } } } class func gcdForArray(_ array: Array<Int>) -> Int { if array.isEmpty { return 1 } var gcd = array[0] for val in array { gcd = UIImage.gcdForPair(val, gcd) } return gcd } class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? { let count = CGImageSourceGetCount(source) var images = [CGImage]() var delays = [Int]() for i in 0..<count { if let image = CGImageSourceCreateImageAtIndex(source, i, nil) { images.append(image) } let delaySeconds = UIImage.delayForImageAtIndex(Int(i), source: source) delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms } let duration: Int = { var sum = 0 for val: Int in delays { sum += val } return sum }() let gcd = gcdForArray(delays) var frames = [UIImage]() var frame: UIImage var frameCount: Int for i in 0..<count { frame = UIImage(cgImage: images[Int(i)]) frameCount = Int(delays[Int(i)] / gcd) for _ in 0..<frameCount { frames.append(frame) } } let animation = UIImage.animatedImage(with: frames, duration: Double(duration) / 1000.0) return animation } } |
5: Now Write func viewDidLoad() & func viewDidAppear(_ animated: Bool) .
1 2 3 4 5 6 7 8 9 10 |
override func viewDidLoad() { super.viewDidLoad() mapIndicator.backgroundColor = AppStaticColors.oneStar sceneView.delegate = self configureLighting() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.loadSaveMap() } |
6: Full Code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
// /** Mobikul Single App @Category Webkul @author Webkul <support@webkul.com> FileName: SuggestionARPathController.swift Copyright (c) 2010-2018 Webkul Software Private Limited (https://webkul.com) @license https://store.webkul.com/license.html */ import UIKit import ARKit class SuggestionARPathController: UIViewController { @IBOutlet weak var sceneView: ARSCNView! @IBOutlet var mapIndicator: UIButton! var mapID:String = "1" @IBAction func dismissController(_ sender: UIButton) { self.dismiss(animated: true, completion: nil) } // func worldMapURL()->URL{ do { let id = mapID return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent("worldMapURL"+id) } catch { fatalError("Error getting world map URL from document directory.") } } override func viewDidLoad() { super.viewDidLoad() mapIndicator.backgroundColor = AppStaticColors.oneStar sceneView.delegate = self configureLighting() } // func resetTrackingConfiguration(with worldMap: ARWorldMap? = nil) { let configuration = ARWorldTrackingConfiguration() configuration.planeDetection = [.horizontal] let options: ARSession.RunOptions = [.resetTracking, .removeExistingAnchors] if let worldMap = worldMap { configuration.initialWorldMap = worldMap mapIndicator.backgroundColor = AppStaticColors.fiveStar mapIndicator.setTitle(mapID, for: .normal) } else { mapIndicator.backgroundColor = AppStaticColors.oneStar } sceneView.debugOptions = [.showFeaturePoints] sceneView.session.run(configuration, options: options) } // func loadSaveMap(){ guard let worldMapData = retrieveWorldMapData(from: worldMapURL()), let worldMap = unarchive(worldMapData: worldMapData) else { return } resetTrackingConfiguration(with: worldMap) } func configureLighting() { sceneView.autoenablesDefaultLighting = true sceneView.automaticallyUpdatesLighting = true } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.loadSaveMap() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) sceneView.session.pause() } func archive(worldMap: ARWorldMap) throws { let data = try NSKeyedArchiver.archivedData(withRootObject: worldMap, requiringSecureCoding: true) try data.write(to: self.worldMapURL(), options: [.atomic]) } func retrieveWorldMapData(from url: URL) -> Data? { do { return try Data(contentsOf: self.worldMapURL()) } catch { return nil } } func unarchive(worldMapData data: Data) -> ARWorldMap? { guard let unarchievedObject = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data), let worldMap = unarchievedObject else { return nil } return worldMap } } extension SuggestionARPathController: ARSCNViewDelegate { func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { guard !(anchor is ARPlaneAnchor) else { return } DispatchQueue.main.async { node.addChildNode(self.displayProductDetailView()) } } } extension SuggestionARPathController{ func displayProductDetailView()->SCNNode { let webViewPlane = SCNPlane(width: 0.15, height: 0.15) webViewPlane.cornerRadius = 0 let webViewNode = SCNNode(geometry: webViewPlane) // Set the web view as webViewPlane's primary texture webViewNode.geometry?.firstMaterial?.diffuse.contents = self.returnAnimationArrow() webViewNode.position.z -= 0.0 // position of images webViewNode.opacity = 0 webViewNode.eulerAngles.x = -.pi / 4 webViewNode.runAction(.sequence([ .wait(duration: 1.0), .fadeOpacity(to: 1.0, duration: 1.0), .moveBy(x: 0, y: 0, z: -0.00, duration: 1.0), .moveBy(x: 0, y: 0, z: -0.00, duration: 0.2) ]) ) return webViewNode } func returnAnimationArrow()->UIImageView{ let jeremyGif = UIImage.gifImageWithName("circle") let imageView = UIImageView(image: jeremyGif) imageView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) return imageView } } |
Now your model will be load with corresponding Map Id.