Display scenes in augmented reality (AR)

Augmented reality (AR) experiences can be implemented with three common patterns: tabletop, flyover, and world-scale.

  • Flyover – With flyover AR you can explore a scene using your device as a window into the virtual world. A typical flyover AR scenario starts with the scene’s virtual camera positioned over an area of interest. You can walk around and reorient the device to focus on specific content in the scene.
  • Tabletop – Tabletop AR provides scene content anchored to a physical surface, as if it were a 3D-printed model. You can walk around the tabletop and view the scene from different angles.
  • World-scale – A kind of AR scenario where scene content is rendered exactly where it would be in the physical world. This is used in scenarios ranging from viewing hidden infrastructure to displaying waypoints for navigation. In AR, the real world, rather than a basemap, provides the context for your GIS data.
FlyoverTabletopWorld-scale
Flyover Tabletop World-scale
On screen, flyover is visually indistinguishable from normal scene rendering.In tabletop, scene content is anchored to a real-world surface.In world-scale AR, scene content is integrated with the real world.

Support for augmented reality is provided through tools available in each ArcGIS Runtime API Toolkit.

Enable your app for AR

  1. See the Toolkit repo on GitHub for the latest instructions for installing.
  2. Add an AR view to your app.
  3. Configure privacy and permissions.
  4. Now you're ready to add tabletop AR, add flyover AR, or add world-scale AR to your app.

Add an AR view to your app

ArcGISARView uses an underlying ARKit or ARCore view and an ArcGIS Runtime AGSSceneView. Use the sceneView method to access the Runtime scene view.

Use the following methods on ArcGISARView to configure AR:

  • translationFactor - controls the relationship between physical device position changes and changes in the position of the scene view's camera. This is useful for tabletop and flyover AR.
  • originCamera - controls the initial position of the scene view's camera. When position tracking is started, ArcGISARView transforms the scene view camera's position using a transformation matrix provided by ARKit or ARCore. Once the origin camera is set, the manipulation of the scene view's camera is handled automatically.
  • setInitialTransformation – takes a point on the screen, finds the surface represented by that point, and applies a transformation such that the origin camera is pinned to the location represented by that point. This is useful for pinning content to a surface, which is needed for tabletop AR.
  • arSCNViewDelegate – forwards messages from ARKit to your app. You can use this to listen for tracking status changes, render content using native iOS rendering (SceneKit).
  • locationChangeHandlerDelegate - forwards messages from the location data source associated with the ArcGISARView to your app. You can use this to listen for location, heading and status changes.

In addition to the toolkit, you'll need to use the following ArcGIS Runtime features provided by the underlying scene view when creating AR experiences:

  • Scene view space effect control — Disable rendering the 'starry sky' effect to display scene content on top of a camera feed.
  • Scene view atmosphere effect control — Disable rendering the atmosphere effect to avoid obscuring rendered content.
  • Surface transparency — Hide the ground when rendering world-scale AR because the camera feed, not the basemap, is providing context for your GIS content. You can use a semitransparent surface to calibrate your position in world-scale AR.
  • Scene view navigation constraint — By default, scene views constrain the camera to being above the ground. You should disable this feature to enable users to use world-scale AR underground (for example, while in a basement). The navigation constraint will interfere with tabletop AR if the user attempts to look at the scene from below.

To use ArcGISARView, first add it to the view, then configure the lifecycle methods to start and stop tracking as needed.

                                                                                                                                                                                                                                                                                                                                               
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// ---------- General --------------------- //

import UIKit
import ARKit
import ArcGISToolkit
import ArcGIS

class BasicARExample: UIViewController {
    private let arView = ArcGISARView()

    override func viewDidLoad() {
        super.viewDidLoad()

        arView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(arView)

        NSLayoutConstraint.activate([
            arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            arView.topAnchor.constraint(equalTo: view.topAnchor),
            arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        arView.startTracking(.ignore)
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        arView.stopTracking()
    }
}


// ---------- Tabletop --------------------- //


    override viewDidLoad() {
        // the rest of viewDidLoad

        // Configure the ArcGISARView’s arSCNViewDelegate to receive status updates
        arView.arSCNViewDelegate = self
    }

    extension BasicARExample: ARSCNViewDelegate {
        public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
            switch camera.trackingState {
            case .normal:
                 helpLabel.isHidden = true
            case .notAvailable:
                helpLabel.text = "Location not available"
            case .limited(let reason):
                switch reason {
                case .excessiveMotion:
                    helpLabel.text = "Try moving your phone more slowly"
                    helpLabel.isHidden = false
                case .initializing:
                    helpLabel.text = "Keep moving your phone"
                    helpLabel.isHidden = false
                case .insufficientFeatures:
                    helpLabel.text = "Try turning on more lights and moving around"
                    helpLabel.isHidden = false
                case .relocalizing:
                    // this won't happen unless you enable relocalization
                    break
                @unknown default:
                   break
                }
            }
        }
    }



    extension BasicARExample: ARSCNViewDelegate {
        //…
        public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            // Only proceed if a plane node was detected
            guard anchor as? ARPlaneAnchor != nil else { return }

            // hasFoundPlane is an instance variable on BasicARExample
            if !hasFoundPlane {
                hasFoundPlane = true
                enableTapToPlace()
            }
        }}

    extension BasicARExample: AGSGeoViewTouchDelegate {
        public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
            // TODO – implement map placement
        }

        private func enableTapToPlace() {
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                // Wait for the user to tap to place the scene
                self.arView.sceneView.touchDelegate = self
            }
        }
    }



    extension BasicARExample: AGSGeoViewTouchDelegate {
        public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
            // Use a screen point to set the initial transformation on the view.
            if self.arView.setInitialTransformation(using: screenPoint) {
                configureSceneForAR()
            } else {
                presentAlert(message: "Failed to place scene, try again")
            }
        }

        private func enableTapToPlace() {
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.helpLabel.isHidden = false
                self.helpLabel.text = "Tap a surface to place the scene"

                // Wait for the user to tap to place the scene
                self.arView.sceneView.touchDelegate = self
            }
        }
    }



    let package = AGSMobileScenePackage(name: "philadelphia")
    private func configureSceneForAR() {
        // Load the package
        package.load { [weak self] (err: Error?) in
            guard let self = self else { return }

            if let error = err {
                self.presentAlert(error: error)
            } else if let scene = self.package.scenes.first {
                // Display the scene
                self.arView.sceneView.scene = scene

                // Configure scene surface opacity and navigation constraint
                if let surface = scene.baseSurface {
                    surface.opacity = 0
                    surface.navigationConstraint = .none
                }

                self.updateTranslationFactorAndOriginCamera(scene) // To do
            }
        }
    }



    private func updateTranslationFactorAndOriginCamera() {
        // Create the origin camera to be at the bottom and in the center of the scene
        let newCam = AGSCamera(latitude: 39.95787000283599, longitude: -75.16996728256345, altitude: 8.813445091247559, heading: 0, pitch: 90, roll: 0)

        // Set the origin camera
        self.arView.originCamera = newCam
    }



    private func updateTranslationFactorAndOriginCamera() {
        // Continued from above...

        // Scene width is about 800m
        let geographicContentWidth = 800.0

        // Physical width of the table area the scene will be placed on in meters
        let tableContainerWidth = 0.5 // About 1.5 feet

        // Set the translation factor based on scene content width and desired physical size
        self.arView.translationFactor = geographicContentWidth / tableContainerWidth

        // Set the origin camera
        self.arView.originCamera = newCam
    }


// ---------- Flyover --------------------- //


    private func configureSceneForAR() {
        // create scene with basemap
        let scene = AGSScene(basemapType: .imagery)

        // create and add mesh layer
        let meshLayer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer/layers/0")!)
        scene.operationalLayers.add(meshLayer)

        // show scene
        arView.sceneView.scene = scene

        // TODO – configure origin camera for AR
    }



     private func configureSceneForAR() {
        // Continued from above

        // Wait for the layer to load, then set the AR camera
        meshLayer.load { [weak self, weak meshLayer] (err: Error?) in
            guard let self = self else { return }
            guard let `meshLayer` = meshLayer else { return }
            if let error = err {
                self.presentAlert(error: error)
            } else if let envelope = meshLayer.fullExtent {
                let camera = AGSCamera(latitude: envelope.center.y,
                                        longitude: envelope.center.x,
                                        altitude: 600,
                                        heading: 0,
                                        pitch: 90,
                                        roll: 0)
                self.arView.originCamera = camera
            }
        }
    }



    private func configureAR() {
        // Continued from above

        // Set the translation factor to enable rapid movement through the scene
        arView.translationFactor = 1000

        // Disable the navigation constraint
        scene.baseSurface?.navigationConstraint = .none

        // Turn the space and atmosphere effects on for an immersive experience
        arView.sceneView.spaceEffect = .stars
        arView.sceneView.atmosphereEffect = .realistic
    }



// ---------- World-scale --------------------- //


    arView.locationDataSource = AGSCLLocationDataSource()



    private func configureSceneForAR() {
        // Create scene with imagery basemap
        let scene = AGSScene(basemapType: .imagery)

        // Create an elevation source and add it to the scene
        let elevationSource = AGSArcGISTiledElevationSource(url:
            URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
        scene.baseSurface?.elevationSources.append(elevationSource)

        // Allow camera to go beneath the surface
        scene.baseSurface?.navigationConstraint = .none

        // Display the scene
        arView.sceneView.scene = scene

        // Configure atmosphere and space effect
        arView.sceneView.spaceEffect = .transparent
        arView.sceneView.atmosphereEffect = .none
    }



    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Continuous update mode
        arView.startTracking(.continuous, completion: nil)

        // One-time update mode
        // arView.startTracking(.initial, completion: nil)
    }



override func viewDidLoad() {
    //…
    arView.arSCNViewDelegate = self
    /…
}

extension ARExample: ARSCNViewDelegate {
    let planeRenderingMaterial = SCNMaterial()
    public init() {
        planeRenderingMaterial.diffuse.contentColor = UIColor(1, 0.5f)
    }
    public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        if shouldHidePlanes {
            return
        }
        guard let _ = anchor as? ARPlaneAnchor else { return }

        // Create a custom object to visualize the plane geometry and extent.
        if #available(iOS 11.3, *) {
            // Place content only for anchors found by plane detection.
            guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

            let arGeometry = planeAnchor.geometry
            let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device!)
            arPlaneSceneGeometry?.update(from: arGeometry)
            let newNode = SCNNode(geometry: arPlaneSceneGeometry)
            node.addChildNode(newNode)
            arPlaneSceneGeometry?.materials = [_planeRenderingMaterial]
            node.geometry = arPlaneSceneGeometry
        }
    }

    public func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        if shouldHidePlanes {
            // Remove plane visualization
            node.removeFromParentNode()
            return
        }

        // Create a custom object to visualize the plane geometry and extent.
        if #available(iOS 11.3, *) {
            // Place content only for anchors found by plane detection.
            guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

            let arGeometry = planeAnchor.geometry
            let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device?)
            arPlaneSceneGeometry?.update(from: arGeometry)
            node.childNodes[0].geometry = arPlaneSceneGeometry
            if let material = node.geometry?.materials {
                arPlaneSceneGeometry?.materials = material
            }
        }
    }
}

Configure privacy and permissions

Before you can use augmented reality, you'll need to request location and camera permissions.

On iOS, ensure the following properties are set in info.plist:

  • Privacy - Camera Usage Description
  • Privacy - Location When In Use Usage Description

The deployment target should be set to a supported version of iOS (see System requirements for details).

If you’d like to restrict your app to installing only on devices that support ARKit, add arkit to the required device capabilities section of info.plist:

      
1
2
3
4
5
6
<!-- -->

<key>UIRequiredDeviceCapabilities</key>
<array>
    <string>arkit</string>
</array>

Once you have installed the toolkit, configured your app to meet privacy requirements, requested location permissions, and added an ArcGISARView to your app, you can begin implementing your AR experience.

Understand Common AR Patterns

There are many AR scenarios you can achieve with Runtime. This SDK recognizes the following common patterns for AR:

  • Flyover – Flyover AR is a kind of AR scenario that allows you to explore a scene using your device as a window into the virtual world. A typical flyover AR scenario will start with the scene’s virtual camera positioned over an area of interest. You can walk around and reorient the device to focus on specific content in the scene.
  • Tabletop – A kind of AR scenario where scene content is anchored to a physical surface, as if it were a 3D-printed model. You can walk around the tabletop and view the scene from different angles.
  • World-scale – A kind of AR scenario where scene content is rendered exactly where it would be in the physical world. This is used in scenarios ranging from viewing hidden infrastructure to displaying waypoints for navigation. In AR, the real world, rather than a basemap, provides the context for your GIS data.

Each experience is built using some combination of the features in Runtime and the toolkit and some basic behavioral assumptions.

AR patternOrigin cameraTranslation factorScene viewBase surface
Flyover ARAbove the tallest content in the sceneA large value to enable rapid traversal; 0 to restrict movementSpace effect: Stars Atmosphere: RealisticDisplayed
Tabletop AROn the ground at the center or lowest point on the sceneBased on the size of the target content and the physical tableSpace effect: Transparent Atmosphere: NoneOptional
World-scale ARAt the same location as the physical device camera1, to keep virtual content in sync with real-world environmentSpace effect: Transparent Atmosphere: NoneOptional for calibration

Add tabletop AR to your app

Tabletop AR allows you to use your device to interact with scenes as if they were 3D-printed models sitting on your desk. You could, for example, use tabletop AR to virtually explore a proposed development without needing to create a physical model.

Implement tabletop AR

Tabletop AR often allows users to place scene content on a physical surface of their choice, such as the top of a desk, for example. Once the content is placed, it stays anchored to the surface as the user moves around it.

  1. Create an ArcGISARView and add it to the view

    See Add an AR view to your app.

  2. Listen for ARKit tracking tracking status changes and provide feedback to the user, for example when the user needs to move the phone more slowly or turn on more lights.

                                                                                                                                                                                                                                                                                                                                                   
    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
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    // ---------- General --------------------- //
    
    import UIKit
    import ARKit
    import ArcGISToolkit
    import ArcGIS
    
    class BasicARExample: UIViewController {
        private let arView = ArcGISARView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            arView.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(arView)
    
            NSLayoutConstraint.activate([
                arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                arView.topAnchor.constraint(equalTo: view.topAnchor),
                arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
                ])
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            arView.startTracking(.ignore)
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            arView.stopTracking()
        }
    }
    
    
    // ---------- Tabletop --------------------- //
    
    
        override viewDidLoad() {
            // the rest of viewDidLoad
    
            // Configure the ArcGISARView’s arSCNViewDelegate to receive status updates
            arView.arSCNViewDelegate = self
        }
    
        extension BasicARExample: ARSCNViewDelegate {
            public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
                switch camera.trackingState {
                case .normal:
                     helpLabel.isHidden = true
                case .notAvailable:
                    helpLabel.text = "Location not available"
                case .limited(let reason):
                    switch reason {
                    case .excessiveMotion:
                        helpLabel.text = "Try moving your phone more slowly"
                        helpLabel.isHidden = false
                    case .initializing:
                        helpLabel.text = "Keep moving your phone"
                        helpLabel.isHidden = false
                    case .insufficientFeatures:
                        helpLabel.text = "Try turning on more lights and moving around"
                        helpLabel.isHidden = false
                    case .relocalizing:
                        // this won't happen unless you enable relocalization
                        break
                    @unknown default:
                       break
                    }
                }
            }
        }
    
    
    
        extension BasicARExample: ARSCNViewDelegate {
            //…
            public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
                // Only proceed if a plane node was detected
                guard anchor as? ARPlaneAnchor != nil else { return }
    
                // hasFoundPlane is an instance variable on BasicARExample
                if !hasFoundPlane {
                    hasFoundPlane = true
                    enableTapToPlace()
                }
            }}
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // TODO – implement map placement
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // Use a screen point to set the initial transformation on the view.
                if self.arView.setInitialTransformation(using: screenPoint) {
                    configureSceneForAR()
                } else {
                    presentAlert(message: "Failed to place scene, try again")
                }
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.helpLabel.isHidden = false
                    self.helpLabel.text = "Tap a surface to place the scene"
    
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        let package = AGSMobileScenePackage(name: "philadelphia")
        private func configureSceneForAR() {
            // Load the package
            package.load { [weak self] (err: Error?) in
                guard let self = self else { return }
    
                if let error = err {
                    self.presentAlert(error: error)
                } else if let scene = self.package.scenes.first {
                    // Display the scene
                    self.arView.sceneView.scene = scene
    
                    // Configure scene surface opacity and navigation constraint
                    if let surface = scene.baseSurface {
                        surface.opacity = 0
                        surface.navigationConstraint = .none
                    }
    
                    self.updateTranslationFactorAndOriginCamera(scene) // To do
                }
            }
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Create the origin camera to be at the bottom and in the center of the scene
            let newCam = AGSCamera(latitude: 39.95787000283599, longitude: -75.16996728256345, altitude: 8.813445091247559, heading: 0, pitch: 90, roll: 0)
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Continued from above...
    
            // Scene width is about 800m
            let geographicContentWidth = 800.0
    
            // Physical width of the table area the scene will be placed on in meters
            let tableContainerWidth = 0.5 // About 1.5 feet
    
            // Set the translation factor based on scene content width and desired physical size
            self.arView.translationFactor = geographicContentWidth / tableContainerWidth
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    // ---------- Flyover --------------------- //
    
    
        private func configureSceneForAR() {
            // create scene with basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // create and add mesh layer
            let meshLayer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer/layers/0")!)
            scene.operationalLayers.add(meshLayer)
    
            // show scene
            arView.sceneView.scene = scene
    
            // TODO – configure origin camera for AR
        }
    
    
    
         private func configureSceneForAR() {
            // Continued from above
    
            // Wait for the layer to load, then set the AR camera
            meshLayer.load { [weak self, weak meshLayer] (err: Error?) in
                guard let self = self else { return }
                guard let `meshLayer` = meshLayer else { return }
                if let error = err {
                    self.presentAlert(error: error)
                } else if let envelope = meshLayer.fullExtent {
                    let camera = AGSCamera(latitude: envelope.center.y,
                                            longitude: envelope.center.x,
                                            altitude: 600,
                                            heading: 0,
                                            pitch: 90,
                                            roll: 0)
                    self.arView.originCamera = camera
                }
            }
        }
    
    
    
        private func configureAR() {
            // Continued from above
    
            // Set the translation factor to enable rapid movement through the scene
            arView.translationFactor = 1000
    
            // Disable the navigation constraint
            scene.baseSurface?.navigationConstraint = .none
    
            // Turn the space and atmosphere effects on for an immersive experience
            arView.sceneView.spaceEffect = .stars
            arView.sceneView.atmosphereEffect = .realistic
        }
    
    
    
    // ---------- World-scale --------------------- //
    
    
        arView.locationDataSource = AGSCLLocationDataSource()
    
    
    
        private func configureSceneForAR() {
            // Create scene with imagery basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // Create an elevation source and add it to the scene
            let elevationSource = AGSArcGISTiledElevationSource(url:
                URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
            scene.baseSurface?.elevationSources.append(elevationSource)
    
            // Allow camera to go beneath the surface
            scene.baseSurface?.navigationConstraint = .none
    
            // Display the scene
            arView.sceneView.scene = scene
    
            // Configure atmosphere and space effect
            arView.sceneView.spaceEffect = .transparent
            arView.sceneView.atmosphereEffect = .none
        }
    
    
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            // Continuous update mode
            arView.startTracking(.continuous, completion: nil)
    
            // One-time update mode
            // arView.startTracking(.initial, completion: nil)
        }
    
    
    
    override func viewDidLoad() {
        //…
        arView.arSCNViewDelegate = self
        /…
    }
    
    extension ARExample: ARSCNViewDelegate {
        let planeRenderingMaterial = SCNMaterial()
        public init() {
            planeRenderingMaterial.diffuse.contentColor = UIColor(1, 0.5f)
        }
        public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                return
            }
            guard let _ = anchor as? ARPlaneAnchor else { return }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device!)
                arPlaneSceneGeometry?.update(from: arGeometry)
                let newNode = SCNNode(geometry: arPlaneSceneGeometry)
                node.addChildNode(newNode)
                arPlaneSceneGeometry?.materials = [_planeRenderingMaterial]
                node.geometry = arPlaneSceneGeometry
            }
        }
    
        public func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                // Remove plane visualization
                node.removeFromParentNode()
                return
            }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device?)
                arPlaneSceneGeometry?.update(from: arGeometry)
                node.childNodes[0].geometry = arPlaneSceneGeometry
                if let material = node.geometry?.materials {
                    arPlaneSceneGeometry?.materials = material
                }
            }
        }
    }
    
  3. When tracking is ready and at least one plane has been found, wait for the user to tap. ARSCNViewDelegate defines renderer(:didAdd node, for anchor) which you can use to detect planes.

                                                                                                                                                                                                                                                                                                                                                   
    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
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    // ---------- General --------------------- //
    
    import UIKit
    import ARKit
    import ArcGISToolkit
    import ArcGIS
    
    class BasicARExample: UIViewController {
        private let arView = ArcGISARView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            arView.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(arView)
    
            NSLayoutConstraint.activate([
                arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                arView.topAnchor.constraint(equalTo: view.topAnchor),
                arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
                ])
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            arView.startTracking(.ignore)
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            arView.stopTracking()
        }
    }
    
    
    // ---------- Tabletop --------------------- //
    
    
        override viewDidLoad() {
            // the rest of viewDidLoad
    
            // Configure the ArcGISARView’s arSCNViewDelegate to receive status updates
            arView.arSCNViewDelegate = self
        }
    
        extension BasicARExample: ARSCNViewDelegate {
            public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
                switch camera.trackingState {
                case .normal:
                     helpLabel.isHidden = true
                case .notAvailable:
                    helpLabel.text = "Location not available"
                case .limited(let reason):
                    switch reason {
                    case .excessiveMotion:
                        helpLabel.text = "Try moving your phone more slowly"
                        helpLabel.isHidden = false
                    case .initializing:
                        helpLabel.text = "Keep moving your phone"
                        helpLabel.isHidden = false
                    case .insufficientFeatures:
                        helpLabel.text = "Try turning on more lights and moving around"
                        helpLabel.isHidden = false
                    case .relocalizing:
                        // this won't happen unless you enable relocalization
                        break
                    @unknown default:
                       break
                    }
                }
            }
        }
    
    
    
        extension BasicARExample: ARSCNViewDelegate {
            //…
            public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
                // Only proceed if a plane node was detected
                guard anchor as? ARPlaneAnchor != nil else { return }
    
                // hasFoundPlane is an instance variable on BasicARExample
                if !hasFoundPlane {
                    hasFoundPlane = true
                    enableTapToPlace()
                }
            }}
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // TODO – implement map placement
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // Use a screen point to set the initial transformation on the view.
                if self.arView.setInitialTransformation(using: screenPoint) {
                    configureSceneForAR()
                } else {
                    presentAlert(message: "Failed to place scene, try again")
                }
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.helpLabel.isHidden = false
                    self.helpLabel.text = "Tap a surface to place the scene"
    
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        let package = AGSMobileScenePackage(name: "philadelphia")
        private func configureSceneForAR() {
            // Load the package
            package.load { [weak self] (err: Error?) in
                guard let self = self else { return }
    
                if let error = err {
                    self.presentAlert(error: error)
                } else if let scene = self.package.scenes.first {
                    // Display the scene
                    self.arView.sceneView.scene = scene
    
                    // Configure scene surface opacity and navigation constraint
                    if let surface = scene.baseSurface {
                        surface.opacity = 0
                        surface.navigationConstraint = .none
                    }
    
                    self.updateTranslationFactorAndOriginCamera(scene) // To do
                }
            }
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Create the origin camera to be at the bottom and in the center of the scene
            let newCam = AGSCamera(latitude: 39.95787000283599, longitude: -75.16996728256345, altitude: 8.813445091247559, heading: 0, pitch: 90, roll: 0)
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Continued from above...
    
            // Scene width is about 800m
            let geographicContentWidth = 800.0
    
            // Physical width of the table area the scene will be placed on in meters
            let tableContainerWidth = 0.5 // About 1.5 feet
    
            // Set the translation factor based on scene content width and desired physical size
            self.arView.translationFactor = geographicContentWidth / tableContainerWidth
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    // ---------- Flyover --------------------- //
    
    
        private func configureSceneForAR() {
            // create scene with basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // create and add mesh layer
            let meshLayer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer/layers/0")!)
            scene.operationalLayers.add(meshLayer)
    
            // show scene
            arView.sceneView.scene = scene
    
            // TODO – configure origin camera for AR
        }
    
    
    
         private func configureSceneForAR() {
            // Continued from above
    
            // Wait for the layer to load, then set the AR camera
            meshLayer.load { [weak self, weak meshLayer] (err: Error?) in
                guard let self = self else { return }
                guard let `meshLayer` = meshLayer else { return }
                if let error = err {
                    self.presentAlert(error: error)
                } else if let envelope = meshLayer.fullExtent {
                    let camera = AGSCamera(latitude: envelope.center.y,
                                            longitude: envelope.center.x,
                                            altitude: 600,
                                            heading: 0,
                                            pitch: 90,
                                            roll: 0)
                    self.arView.originCamera = camera
                }
            }
        }
    
    
    
        private func configureAR() {
            // Continued from above
    
            // Set the translation factor to enable rapid movement through the scene
            arView.translationFactor = 1000
    
            // Disable the navigation constraint
            scene.baseSurface?.navigationConstraint = .none
    
            // Turn the space and atmosphere effects on for an immersive experience
            arView.sceneView.spaceEffect = .stars
            arView.sceneView.atmosphereEffect = .realistic
        }
    
    
    
    // ---------- World-scale --------------------- //
    
    
        arView.locationDataSource = AGSCLLocationDataSource()
    
    
    
        private func configureSceneForAR() {
            // Create scene with imagery basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // Create an elevation source and add it to the scene
            let elevationSource = AGSArcGISTiledElevationSource(url:
                URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
            scene.baseSurface?.elevationSources.append(elevationSource)
    
            // Allow camera to go beneath the surface
            scene.baseSurface?.navigationConstraint = .none
    
            // Display the scene
            arView.sceneView.scene = scene
    
            // Configure atmosphere and space effect
            arView.sceneView.spaceEffect = .transparent
            arView.sceneView.atmosphereEffect = .none
        }
    
    
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            // Continuous update mode
            arView.startTracking(.continuous, completion: nil)
    
            // One-time update mode
            // arView.startTracking(.initial, completion: nil)
        }
    
    
    
    override func viewDidLoad() {
        //…
        arView.arSCNViewDelegate = self
        /…
    }
    
    extension ARExample: ARSCNViewDelegate {
        let planeRenderingMaterial = SCNMaterial()
        public init() {
            planeRenderingMaterial.diffuse.contentColor = UIColor(1, 0.5f)
        }
        public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                return
            }
            guard let _ = anchor as? ARPlaneAnchor else { return }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device!)
                arPlaneSceneGeometry?.update(from: arGeometry)
                let newNode = SCNNode(geometry: arPlaneSceneGeometry)
                node.addChildNode(newNode)
                arPlaneSceneGeometry?.materials = [_planeRenderingMaterial]
                node.geometry = arPlaneSceneGeometry
            }
        }
    
        public func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                // Remove plane visualization
                node.removeFromParentNode()
                return
            }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device?)
                arPlaneSceneGeometry?.update(from: arGeometry)
                node.childNodes[0].geometry = arPlaneSceneGeometry
                if let material = node.geometry?.materials {
                    arPlaneSceneGeometry?.materials = material
                }
            }
        }
    }
    
  4. Once the user has tapped a point, call setInitialTransformation . The toolkit will use the native platform’s plane detection to position the virtual camera relative to the plane. If the result is true, the transformation has been set successfully and you can place the scene.

                                                                                                                                                                                                                                                                                                                                                   
    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
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    // ---------- General --------------------- //
    
    import UIKit
    import ARKit
    import ArcGISToolkit
    import ArcGIS
    
    class BasicARExample: UIViewController {
        private let arView = ArcGISARView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            arView.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(arView)
    
            NSLayoutConstraint.activate([
                arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                arView.topAnchor.constraint(equalTo: view.topAnchor),
                arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
                ])
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            arView.startTracking(.ignore)
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            arView.stopTracking()
        }
    }
    
    
    // ---------- Tabletop --------------------- //
    
    
        override viewDidLoad() {
            // the rest of viewDidLoad
    
            // Configure the ArcGISARView’s arSCNViewDelegate to receive status updates
            arView.arSCNViewDelegate = self
        }
    
        extension BasicARExample: ARSCNViewDelegate {
            public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
                switch camera.trackingState {
                case .normal:
                     helpLabel.isHidden = true
                case .notAvailable:
                    helpLabel.text = "Location not available"
                case .limited(let reason):
                    switch reason {
                    case .excessiveMotion:
                        helpLabel.text = "Try moving your phone more slowly"
                        helpLabel.isHidden = false
                    case .initializing:
                        helpLabel.text = "Keep moving your phone"
                        helpLabel.isHidden = false
                    case .insufficientFeatures:
                        helpLabel.text = "Try turning on more lights and moving around"
                        helpLabel.isHidden = false
                    case .relocalizing:
                        // this won't happen unless you enable relocalization
                        break
                    @unknown default:
                       break
                    }
                }
            }
        }
    
    
    
        extension BasicARExample: ARSCNViewDelegate {
            //…
            public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
                // Only proceed if a plane node was detected
                guard anchor as? ARPlaneAnchor != nil else { return }
    
                // hasFoundPlane is an instance variable on BasicARExample
                if !hasFoundPlane {
                    hasFoundPlane = true
                    enableTapToPlace()
                }
            }}
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // TODO – implement map placement
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // Use a screen point to set the initial transformation on the view.
                if self.arView.setInitialTransformation(using: screenPoint) {
                    configureSceneForAR()
                } else {
                    presentAlert(message: "Failed to place scene, try again")
                }
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.helpLabel.isHidden = false
                    self.helpLabel.text = "Tap a surface to place the scene"
    
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        let package = AGSMobileScenePackage(name: "philadelphia")
        private func configureSceneForAR() {
            // Load the package
            package.load { [weak self] (err: Error?) in
                guard let self = self else { return }
    
                if let error = err {
                    self.presentAlert(error: error)
                } else if let scene = self.package.scenes.first {
                    // Display the scene
                    self.arView.sceneView.scene = scene
    
                    // Configure scene surface opacity and navigation constraint
                    if let surface = scene.baseSurface {
                        surface.opacity = 0
                        surface.navigationConstraint = .none
                    }
    
                    self.updateTranslationFactorAndOriginCamera(scene) // To do
                }
            }
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Create the origin camera to be at the bottom and in the center of the scene
            let newCam = AGSCamera(latitude: 39.95787000283599, longitude: -75.16996728256345, altitude: 8.813445091247559, heading: 0, pitch: 90, roll: 0)
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Continued from above...
    
            // Scene width is about 800m
            let geographicContentWidth = 800.0
    
            // Physical width of the table area the scene will be placed on in meters
            let tableContainerWidth = 0.5 // About 1.5 feet
    
            // Set the translation factor based on scene content width and desired physical size
            self.arView.translationFactor = geographicContentWidth / tableContainerWidth
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    // ---------- Flyover --------------------- //
    
    
        private func configureSceneForAR() {
            // create scene with basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // create and add mesh layer
            let meshLayer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer/layers/0")!)
            scene.operationalLayers.add(meshLayer)
    
            // show scene
            arView.sceneView.scene = scene
    
            // TODO – configure origin camera for AR
        }
    
    
    
         private func configureSceneForAR() {
            // Continued from above
    
            // Wait for the layer to load, then set the AR camera
            meshLayer.load { [weak self, weak meshLayer] (err: Error?) in
                guard let self = self else { return }
                guard let `meshLayer` = meshLayer else { return }
                if let error = err {
                    self.presentAlert(error: error)
                } else if let envelope = meshLayer.fullExtent {
                    let camera = AGSCamera(latitude: envelope.center.y,
                                            longitude: envelope.center.x,
                                            altitude: 600,
                                            heading: 0,
                                            pitch: 90,
                                            roll: 0)
                    self.arView.originCamera = camera
                }
            }
        }
    
    
    
        private func configureAR() {
            // Continued from above
    
            // Set the translation factor to enable rapid movement through the scene
            arView.translationFactor = 1000
    
            // Disable the navigation constraint
            scene.baseSurface?.navigationConstraint = .none
    
            // Turn the space and atmosphere effects on for an immersive experience
            arView.sceneView.spaceEffect = .stars
            arView.sceneView.atmosphereEffect = .realistic
        }
    
    
    
    // ---------- World-scale --------------------- //
    
    
        arView.locationDataSource = AGSCLLocationDataSource()
    
    
    
        private func configureSceneForAR() {
            // Create scene with imagery basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // Create an elevation source and add it to the scene
            let elevationSource = AGSArcGISTiledElevationSource(url:
                URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
            scene.baseSurface?.elevationSources.append(elevationSource)
    
            // Allow camera to go beneath the surface
            scene.baseSurface?.navigationConstraint = .none
    
            // Display the scene
            arView.sceneView.scene = scene
    
            // Configure atmosphere and space effect
            arView.sceneView.spaceEffect = .transparent
            arView.sceneView.atmosphereEffect = .none
        }
    
    
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            // Continuous update mode
            arView.startTracking(.continuous, completion: nil)
    
            // One-time update mode
            // arView.startTracking(.initial, completion: nil)
        }
    
    
    
    override func viewDidLoad() {
        //…
        arView.arSCNViewDelegate = self
        /…
    }
    
    extension ARExample: ARSCNViewDelegate {
        let planeRenderingMaterial = SCNMaterial()
        public init() {
            planeRenderingMaterial.diffuse.contentColor = UIColor(1, 0.5f)
        }
        public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                return
            }
            guard let _ = anchor as? ARPlaneAnchor else { return }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device!)
                arPlaneSceneGeometry?.update(from: arGeometry)
                let newNode = SCNNode(geometry: arPlaneSceneGeometry)
                node.addChildNode(newNode)
                arPlaneSceneGeometry?.materials = [_planeRenderingMaterial]
                node.geometry = arPlaneSceneGeometry
            }
        }
    
        public func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                // Remove plane visualization
                node.removeFromParentNode()
                return
            }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device?)
                arPlaneSceneGeometry?.update(from: arGeometry)
                node.childNodes[0].geometry = arPlaneSceneGeometry
                if let material = node.geometry?.materials {
                    arPlaneSceneGeometry?.materials = material
                }
            }
        }
    }
    
  5. Create and display the scene. Set the navigation constraint on the scene’s base surface to .none .

    For demonstration purposes, this code uses the Philadelphia mobile scene package because it is particularly well-suited for tabletop display. You can download that .mspk and add it to your project to make the code below work. Alternatively, you can use any scene for tabletop mapping, but be sure to define a clipping distance for a proper tabletop experience.

                                                                                                                                                                                                                                                                                                                                                   
    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
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    // ---------- General --------------------- //
    
    import UIKit
    import ARKit
    import ArcGISToolkit
    import ArcGIS
    
    class BasicARExample: UIViewController {
        private let arView = ArcGISARView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            arView.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(arView)
    
            NSLayoutConstraint.activate([
                arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                arView.topAnchor.constraint(equalTo: view.topAnchor),
                arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
                ])
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            arView.startTracking(.ignore)
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            arView.stopTracking()
        }
    }
    
    
    // ---------- Tabletop --------------------- //
    
    
        override viewDidLoad() {
            // the rest of viewDidLoad
    
            // Configure the ArcGISARView’s arSCNViewDelegate to receive status updates
            arView.arSCNViewDelegate = self
        }
    
        extension BasicARExample: ARSCNViewDelegate {
            public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
                switch camera.trackingState {
                case .normal:
                     helpLabel.isHidden = true
                case .notAvailable:
                    helpLabel.text = "Location not available"
                case .limited(let reason):
                    switch reason {
                    case .excessiveMotion:
                        helpLabel.text = "Try moving your phone more slowly"
                        helpLabel.isHidden = false
                    case .initializing:
                        helpLabel.text = "Keep moving your phone"
                        helpLabel.isHidden = false
                    case .insufficientFeatures:
                        helpLabel.text = "Try turning on more lights and moving around"
                        helpLabel.isHidden = false
                    case .relocalizing:
                        // this won't happen unless you enable relocalization
                        break
                    @unknown default:
                       break
                    }
                }
            }
        }
    
    
    
        extension BasicARExample: ARSCNViewDelegate {
            //…
            public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
                // Only proceed if a plane node was detected
                guard anchor as? ARPlaneAnchor != nil else { return }
    
                // hasFoundPlane is an instance variable on BasicARExample
                if !hasFoundPlane {
                    hasFoundPlane = true
                    enableTapToPlace()
                }
            }}
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // TODO – implement map placement
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // Use a screen point to set the initial transformation on the view.
                if self.arView.setInitialTransformation(using: screenPoint) {
                    configureSceneForAR()
                } else {
                    presentAlert(message: "Failed to place scene, try again")
                }
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.helpLabel.isHidden = false
                    self.helpLabel.text = "Tap a surface to place the scene"
    
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        let package = AGSMobileScenePackage(name: "philadelphia")
        private func configureSceneForAR() {
            // Load the package
            package.load { [weak self] (err: Error?) in
                guard let self = self else { return }
    
                if let error = err {
                    self.presentAlert(error: error)
                } else if let scene = self.package.scenes.first {
                    // Display the scene
                    self.arView.sceneView.scene = scene
    
                    // Configure scene surface opacity and navigation constraint
                    if let surface = scene.baseSurface {
                        surface.opacity = 0
                        surface.navigationConstraint = .none
                    }
    
                    self.updateTranslationFactorAndOriginCamera(scene) // To do
                }
            }
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Create the origin camera to be at the bottom and in the center of the scene
            let newCam = AGSCamera(latitude: 39.95787000283599, longitude: -75.16996728256345, altitude: 8.813445091247559, heading: 0, pitch: 90, roll: 0)
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Continued from above...
    
            // Scene width is about 800m
            let geographicContentWidth = 800.0
    
            // Physical width of the table area the scene will be placed on in meters
            let tableContainerWidth = 0.5 // About 1.5 feet
    
            // Set the translation factor based on scene content width and desired physical size
            self.arView.translationFactor = geographicContentWidth / tableContainerWidth
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    // ---------- Flyover --------------------- //
    
    
        private func configureSceneForAR() {
            // create scene with basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // create and add mesh layer
            let meshLayer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer/layers/0")!)
            scene.operationalLayers.add(meshLayer)
    
            // show scene
            arView.sceneView.scene = scene
    
            // TODO – configure origin camera for AR
        }
    
    
    
         private func configureSceneForAR() {
            // Continued from above
    
            // Wait for the layer to load, then set the AR camera
            meshLayer.load { [weak self, weak meshLayer] (err: Error?) in
                guard let self = self else { return }
                guard let `meshLayer` = meshLayer else { return }
                if let error = err {
                    self.presentAlert(error: error)
                } else if let envelope = meshLayer.fullExtent {
                    let camera = AGSCamera(latitude: envelope.center.y,
                                            longitude: envelope.center.x,
                                            altitude: 600,
                                            heading: 0,
                                            pitch: 90,
                                            roll: 0)
                    self.arView.originCamera = camera
                }
            }
        }
    
    
    
        private func configureAR() {
            // Continued from above
    
            // Set the translation factor to enable rapid movement through the scene
            arView.translationFactor = 1000
    
            // Disable the navigation constraint
            scene.baseSurface?.navigationConstraint = .none
    
            // Turn the space and atmosphere effects on for an immersive experience
            arView.sceneView.spaceEffect = .stars
            arView.sceneView.atmosphereEffect = .realistic
        }
    
    
    
    // ---------- World-scale --------------------- //
    
    
        arView.locationDataSource = AGSCLLocationDataSource()
    
    
    
        private func configureSceneForAR() {
            // Create scene with imagery basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // Create an elevation source and add it to the scene
            let elevationSource = AGSArcGISTiledElevationSource(url:
                URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
            scene.baseSurface?.elevationSources.append(elevationSource)
    
            // Allow camera to go beneath the surface
            scene.baseSurface?.navigationConstraint = .none
    
            // Display the scene
            arView.sceneView.scene = scene
    
            // Configure atmosphere and space effect
            arView.sceneView.spaceEffect = .transparent
            arView.sceneView.atmosphereEffect = .none
        }
    
    
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            // Continuous update mode
            arView.startTracking(.continuous, completion: nil)
    
            // One-time update mode
            // arView.startTracking(.initial, completion: nil)
        }
    
    
    
    override func viewDidLoad() {
        //…
        arView.arSCNViewDelegate = self
        /…
    }
    
    extension ARExample: ARSCNViewDelegate {
        let planeRenderingMaterial = SCNMaterial()
        public init() {
            planeRenderingMaterial.diffuse.contentColor = UIColor(1, 0.5f)
        }
        public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                return
            }
            guard let _ = anchor as? ARPlaneAnchor else { return }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device!)
                arPlaneSceneGeometry?.update(from: arGeometry)
                let newNode = SCNNode(geometry: arPlaneSceneGeometry)
                node.addChildNode(newNode)
                arPlaneSceneGeometry?.materials = [_planeRenderingMaterial]
                node.geometry = arPlaneSceneGeometry
            }
        }
    
        public func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                // Remove plane visualization
                node.removeFromParentNode()
                return
            }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device?)
                arPlaneSceneGeometry?.update(from: arGeometry)
                node.childNodes[0].geometry = arPlaneSceneGeometry
                if let material = node.geometry?.materials {
                    arPlaneSceneGeometry?.materials = material
                }
            }
        }
    }
    
  6. Find an anchor point in the scene. You can use a known value, a user-selected value, or a computed value. For simplicity, this example uses a known value. Place the origin camera at that point.

                                                                                                                                                                                                                                                                                                                                                   
    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
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    // ---------- General --------------------- //
    
    import UIKit
    import ARKit
    import ArcGISToolkit
    import ArcGIS
    
    class BasicARExample: UIViewController {
        private let arView = ArcGISARView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            arView.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(arView)
    
            NSLayoutConstraint.activate([
                arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                arView.topAnchor.constraint(equalTo: view.topAnchor),
                arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
                ])
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            arView.startTracking(.ignore)
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            arView.stopTracking()
        }
    }
    
    
    // ---------- Tabletop --------------------- //
    
    
        override viewDidLoad() {
            // the rest of viewDidLoad
    
            // Configure the ArcGISARView’s arSCNViewDelegate to receive status updates
            arView.arSCNViewDelegate = self
        }
    
        extension BasicARExample: ARSCNViewDelegate {
            public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
                switch camera.trackingState {
                case .normal:
                     helpLabel.isHidden = true
                case .notAvailable:
                    helpLabel.text = "Location not available"
                case .limited(let reason):
                    switch reason {
                    case .excessiveMotion:
                        helpLabel.text = "Try moving your phone more slowly"
                        helpLabel.isHidden = false
                    case .initializing:
                        helpLabel.text = "Keep moving your phone"
                        helpLabel.isHidden = false
                    case .insufficientFeatures:
                        helpLabel.text = "Try turning on more lights and moving around"
                        helpLabel.isHidden = false
                    case .relocalizing:
                        // this won't happen unless you enable relocalization
                        break
                    @unknown default:
                       break
                    }
                }
            }
        }
    
    
    
        extension BasicARExample: ARSCNViewDelegate {
            //…
            public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
                // Only proceed if a plane node was detected
                guard anchor as? ARPlaneAnchor != nil else { return }
    
                // hasFoundPlane is an instance variable on BasicARExample
                if !hasFoundPlane {
                    hasFoundPlane = true
                    enableTapToPlace()
                }
            }}
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // TODO – implement map placement
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        extension BasicARExample: AGSGeoViewTouchDelegate {
            public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
                // Use a screen point to set the initial transformation on the view.
                if self.arView.setInitialTransformation(using: screenPoint) {
                    configureSceneForAR()
                } else {
                    presentAlert(message: "Failed to place scene, try again")
                }
            }
    
            private func enableTapToPlace() {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.helpLabel.isHidden = false
                    self.helpLabel.text = "Tap a surface to place the scene"
    
                    // Wait for the user to tap to place the scene
                    self.arView.sceneView.touchDelegate = self
                }
            }
        }
    
    
    
        let package = AGSMobileScenePackage(name: "philadelphia")
        private func configureSceneForAR() {
            // Load the package
            package.load { [weak self] (err: Error?) in
                guard let self = self else { return }
    
                if let error = err {
                    self.presentAlert(error: error)
                } else if let scene = self.package.scenes.first {
                    // Display the scene
                    self.arView.sceneView.scene = scene
    
                    // Configure scene surface opacity and navigation constraint
                    if let surface = scene.baseSurface {
                        surface.opacity = 0
                        surface.navigationConstraint = .none
                    }
    
                    self.updateTranslationFactorAndOriginCamera(scene) // To do
                }
            }
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Create the origin camera to be at the bottom and in the center of the scene
            let newCam = AGSCamera(latitude: 39.95787000283599, longitude: -75.16996728256345, altitude: 8.813445091247559, heading: 0, pitch: 90, roll: 0)
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    
        private func updateTranslationFactorAndOriginCamera() {
            // Continued from above...
    
            // Scene width is about 800m
            let geographicContentWidth = 800.0
    
            // Physical width of the table area the scene will be placed on in meters
            let tableContainerWidth = 0.5 // About 1.5 feet
    
            // Set the translation factor based on scene content width and desired physical size
            self.arView.translationFactor = geographicContentWidth / tableContainerWidth
    
            // Set the origin camera
            self.arView.originCamera = newCam
        }
    
    
    // ---------- Flyover --------------------- //
    
    
        private func configureSceneForAR() {
            // create scene with basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // create and add mesh layer
            let meshLayer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/u0sSNqDXr7puKJrF/arcgis/rest/services/Frankfurt2017_v17/SceneServer/layers/0")!)
            scene.operationalLayers.add(meshLayer)
    
            // show scene
            arView.sceneView.scene = scene
    
            // TODO – configure origin camera for AR
        }
    
    
    
         private func configureSceneForAR() {
            // Continued from above
    
            // Wait for the layer to load, then set the AR camera
            meshLayer.load { [weak self, weak meshLayer] (err: Error?) in
                guard let self = self else { return }
                guard let `meshLayer` = meshLayer else { return }
                if let error = err {
                    self.presentAlert(error: error)
                } else if let envelope = meshLayer.fullExtent {
                    let camera = AGSCamera(latitude: envelope.center.y,
                                            longitude: envelope.center.x,
                                            altitude: 600,
                                            heading: 0,
                                            pitch: 90,
                                            roll: 0)
                    self.arView.originCamera = camera
                }
            }
        }
    
    
    
        private func configureAR() {
            // Continued from above
    
            // Set the translation factor to enable rapid movement through the scene
            arView.translationFactor = 1000
    
            // Disable the navigation constraint
            scene.baseSurface?.navigationConstraint = .none
    
            // Turn the space and atmosphere effects on for an immersive experience
            arView.sceneView.spaceEffect = .stars
            arView.sceneView.atmosphereEffect = .realistic
        }
    
    
    
    // ---------- World-scale --------------------- //
    
    
        arView.locationDataSource = AGSCLLocationDataSource()
    
    
    
        private func configureSceneForAR() {
            // Create scene with imagery basemap
            let scene = AGSScene(basemapType: .imagery)
    
            // Create an elevation source and add it to the scene
            let elevationSource = AGSArcGISTiledElevationSource(url:
                URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
            scene.baseSurface?.elevationSources.append(elevationSource)
    
            // Allow camera to go beneath the surface
            scene.baseSurface?.navigationConstraint = .none
    
            // Display the scene
            arView.sceneView.scene = scene
    
            // Configure atmosphere and space effect
            arView.sceneView.spaceEffect = .transparent
            arView.sceneView.atmosphereEffect = .none
        }
    
    
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            // Continuous update mode
            arView.startTracking(.continuous, completion: nil)
    
            // One-time update mode
            // arView.startTracking(.initial, completion: nil)
        }
    
    
    
    override func viewDidLoad() {
        //…
        arView.arSCNViewDelegate = self
        /…
    }
    
    extension ARExample: ARSCNViewDelegate {
        let planeRenderingMaterial = SCNMaterial()
        public init() {
            planeRenderingMaterial.diffuse.contentColor = UIColor(1, 0.5f)
        }
        public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                return
            }
            guard let _ = anchor as? ARPlaneAnchor else { return }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device!)
                arPlaneSceneGeometry?.update(from: arGeometry)
                let newNode = SCNNode(geometry: arPlaneSceneGeometry)
                node.addChildNode(newNode)
                arPlaneSceneGeometry?.materials = [_planeRenderingMaterial]
                node.geometry = arPlaneSceneGeometry
            }
        }
    
        public func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
            if shouldHidePlanes {
                // Remove plane visualization
                node.removeFromParentNode()
                return
            }
    
            // Create a custom object to visualize the plane geometry and extent.
            if #available(iOS 11.3, *) {
                // Place content only for anchors found by plane detection.
                guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
                let arGeometry = planeAnchor.geometry
                let arPlaneSceneGeometry = ARSCNPlaneGeometry(device: renderer.device?)
                arPlaneSceneGeometry?.update(from: arGeometry)
                node.childNodes[0].geometry = arPlaneSceneGeometry
                if let material = node.geometry?.materials {
                    arPlaneSceneGeometry?.materials = material
                }
            }
        }
    }
    
  7. Set the translation factor on the ArcGIS AR view so that the whole scene can be viewed by moving around it. A useful formula for determining this value is translation factor = virtual content width / desired physical content width. The desired physical content width is the size of the physical table while virtual content width is the real-world size of the scene content; both measurements should be in meters. You can set the virtual content width by setting a clipping distance.

                                                                                                                                                                                                                                                                                                                                                   
    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
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    // ---------- General --------------------- //
    
    import UIKit
    import ARKit
    import ArcGISToolkit
    import ArcGIS
    
    class BasicARExample: UIViewController {
        private let arView = ArcGISARView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            arView.translatesAutoresizingMaskIntoConstraints = false