Migrate from 100.x to 200.x

Introduction

ArcGIS Maps SDK for Swift v200.x provides the next-generation mapping API designed for Swift developers. It is the successor to ArcGIS Runtime SDK for iOS v100.x and inherits the same underlying foundation, architecture, and capabilities. Many of the API concepts remain unchanged, however they are written to align with Swift conventions, frameworks, and features, such as SwiftUI, structured concurrency, value types, measurement, default parameters, and so on.

Migrating existing apps requires significant changes, specifically rebuilding the user interface with SwiftUI and implementing asynchronous code with async/await. A solid understanding of these technologies will help ease your transition and provide a solid foundation.

API Name Changes

Type Name Changes

Types (classes, protocols, structs, and enums) are encapsulated in the ArcGIS module and no longer have the AGS prefix in their name. Here are some examples of the resulting name changes:

v100.xv200.x
AGSMapMap
AGSMapViewMapView
AGSLoadableLoadable
AGSViewpointViewpoint
AGSPointPoint
AGSFeatureLayerFeatureLayer
AGSArcGISFeatureTableArcGISFeatureTable

The word Runtime has been dropped from type names as part of product renaming to ArcGIS Maps SDK

v100.xv200.x
ArcGISRuntimeEnvironmentArcGISEnvironment

Method and Property Name Changes

Various methods and properties have been adjusted to follow the Swift API Design Guidelines.

  • Method arguments have more readable labels.

100.x:

Use dark colors for code blocksCopy
1
AGSGeometryEngine.bufferGeometry(geometry, byDistance: 5000)

200.x:

Use dark colors for code blocksCopy
1
GeometryEngine.buffer(around: geometry, distance: 5000)
  • Method arguments have been reordered.

100.x:

Use dark colors for code blocksCopy
1
featureLayer.setFeature(feature, visible: true)

200.x:

Use dark colors for code blocksCopy
1
featureLayer.setVisible(true, for: feature)
  • Boolean properties read as assertions.

100.x:

Use dark colors for code blocksCopy
1
trackingStatus.calculatingRoute

200.x:

Use dark colors for code blocksCopy
1
trackingStatus.isCalculatingRoute
  • Factory methods use a make prefix.

100.x:

Use dark colors for code blocksCopy
1
featureTable.createFeature()

200.x:

Use dark colors for code blocksCopy
1
featureTable.makeFeature()
  • Classes that contain collections provide mutating methods to modify them.

100.x:

Use dark colors for code blocksCopy
1
graphicsOverlay.graphics.add(graphic)

200.x:

Use dark colors for code blocksCopy
1
graphicsOverlay.addGraphic(graphic)

Invoking asynchronous methods no longer requires passing in a closure callback to handle completion. You can await the asynchronous method and directly use the result or handle the error.

100.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
routeTask.solveRoute(with: routeParameters) { (routeResult: AGSRouteResult?, error: Error?) in
    if let error {
        // Handle error.
    } else if let routeResult {
        // Use the result.
    }
}

200.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
do {
    let routeResult = try await routeTask.solveRoute(using: routeParameters)
    // Use the result.
} catch {
    // Handle error.
}

Starting a job no longer requires passing in closure callbacks to monitor status or handle completion. Instead, you can start a job and await its output to get the result, and asynchronously iterate through its messages to monitor the status.

100.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let job = offlineMapTask.generateOfflineMapJob(
    with: parameters,
    downloadDirectory: downloadDirectoryURL
)
job.start(statusHandler: { status in
    print(status)
}, completion: { [weak self] (result, error) in
    guard let self = self else { return }
    if let result {
        self.offlineMapGenerationDidSucceed(with: result)
    } else if let error {
        self.offlineMapGenerationDidFail(with: error)
    }
})

200.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let job = offlineMapTask.makeGenerateOfflineMapJob(
    parameters: parameters,
    downloadDirectory: downloadDirectoryURL
)
job.start()
Task {
    for await status in job.$status {
        print(status)
    }
}
do {
    let output = try await job.output
} catch {
    self.error = error
}

Events are provided through asynchronous sequences instead of through callback handlers, delegates, or the notification center.

100.x:

Use dark colors for code blocksCopy
1
2
3
locationDisplay.dataSourceStatusChangedHandler = { [weak self] isStarted in
    // Check if started.
}

200.x:

Use dark colors for code blocksCopy
1
2
3
for await isStarted in locationDisplay.statusChanged {
    // Check if started.
}

Specifically, for delegate methods, such as in route tracker provided by AGSRouteTrackerDelegate, the new matching pattern is to use an asynchronous stream.

100.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
extension NavigateRouteViewController: AGSRouteTrackerDelegate {
    func routeTracker(_ routeTracker: AGSRouteTracker, didUpdate trackingStatus: AGSTrackingStatus) {
        routeRemainingGraphic.geometry = trackingStatus.routeProgress.remainingGeometry
        routeTraversedGraphic.geometry = trackingStatus.routeProgress.traversedGeometry
    }
}

200.x:

Use dark colors for code blocksCopy
1
2
3
4
for try await newStatus in routeTracker.$trackingStatus {
    routeRemainingGraphic.geometry = newStatus?.routeProgress.remainingGeometry
    routeTraversedGraphic.geometry = newStatus?.routeProgress.traversedGeometry
}

Create a Map View

SwiftUI is a declarative UI framework. This means that when you create a View, you also create a description of what to display with a given state. This is a change from a UIView in UIKit, where you create an view with a defined frame. In SwiftUI, views are contained in the body of a parent view; but in UIKit views were added to the view controller's root view either programmatically or using Storyboard. The following code shows how to create a map view:

100.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
@IBOutlet private var mapView: AGSMapView! {
    didSet {
        mapView.map = AGSMap(basemapStyle: .arcGISOceans)
        mapView.setViewpoint(AGSViewpoint(latitude: -117, longitude: 34, scale: 1e5))
    }
}

200.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// A model to store various model objects required by the geo view.
private class Model: ObservableObject {
    let map = Map(basemapStyle: .arcGISOceans)
}

// Held as a StateObject so it doesn't get recreated when the view's state changes.
@StateObject private var model = Model()

// The current viewpoint of the map view.
@State private var viewpoint = Viewpoint(
    center: Point(x: -117, y: 34, spatialReference: .wgs84),
    scale: 1e5
)

var body: some View {
    MapView(
        map: model.map,
        viewpoint: viewpoint
    )
}

View Modifiers

View modifiers are applied to the views to customize their appearance and behavior. The new MapView and SceneView have various view modifiers to achieve the same functionalities as their UIKit counterparts.

  • Viewpoint: Update the viewpoint state variable with the current viewpoint of the map view.
Use dark colors for code blocksCopy
1
.onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
  • DrawStatus: Perform actions when the map view draw status changes.
Use dark colors for code blocksCopy
1
2
3
.onDrawStatusChanged {
    if $0 == .completed { print("Map view draw completed.") }
}
  • LocationDisplay: Turn on and configure the location display in the map view.
Use dark colors for code blocksCopy
1
.locationDisplay(model.locationDisplay)
  • Callout: Display a callout with the coordinates of the tapped point in the map view.

The gesture delegate methods in AGSGeoViewTouchDelegate are now replaced by various view modifiers.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.onSingleTapGesture { screenPoint, mapPoint in
    if calloutPlacement == nil {
        // Shows the callout at the tapped location in WGS 84.
        calloutPlacement = LocationCalloutPlacement(location: mapPoint)
    } else {
        // Hides the callout.
        calloutPlacement = nil
    }
}
.callout(placement: $calloutPlacement.animation(.default.speed(4))) { callout in
    Text(
        CoordinateFormatter.toLatitudeLongitude(
            point: callout.location,
            format: .decimalDegrees,
            decimalPlaces: 2
        )
    )
    .font(.callout)
}

Identify a Geoelement

Due to differences between UIKit and SwiftUI, performing operations such as identify cannot be performed on the map view directly. In v200.x, we follow SwiftUI's reader-proxy design pattern, and introduce MapViewProxy to allow operations such as identify to be performed.

The following code shows the difference between identifying using AGSGeoView.identifyLayers in v100.15 vs. using MapViewProxy.identify in v200.x.

100.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Completion block.
mapView.identifyLayer(
    featureLayer,
    screenPoint: screenPoint,
    tolerance: 12,
    returnPopupsOnly: false,
    maximumResults: 10
) { (results: [AGSIdentifyLayerResult]?, error: Error?) in
    if let error {
        self.presentAlert(error: error)
    } else if let results {
        self.handleIdentifyResults(results)
    }
}

200.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MapViewReader { mapViewProxy in
    MapView(map: map)
        .onSingleTapGesture { screenPoint, tapLocation in
            identifyScreenPoint = screenPoint
            identifyTapLocation = tapLocation
        }
        .task(id: identifyScreenPoint) {
            guard let screenPoint = identifyScreenPoint else { return }
            // Async/Await.
            do {
                let results = try await mapViewProxy.identify(
                    on: featureLayer,
                    screenPoint: screenPoint,
                    tolerance: 12,
                    maximumResults: 10
                )
            } catch {
                self.error = error
            }
            // Handles the identify results below.
        }
}

@State and @StateObject

View updates in SwiftUI are driven by state changes. The following code shows getting the latest visible area of a map view in v100.15 compared to v200.x.

100.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
@IBOutlet private var mapView: AGSMapView! {
    didSet {
        mapView.map = AGSMap(basemapStyle: .arcGISOceans)
    }
}

// Get the visible area using…
private var visibleArea: AGSPolygon? {
    mapView.visibleArea
}

200.x:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private class Model: ObservableObject {
    let map = Map(basemapStyle: .arcGISOceans)
}

@StateObject private var model = Model()

/// The visible area of the current map view.
/// Can be used in an overview map.
@State private var visibleArea: Polygon?

var body: some View {
    MapView(map: model.map)
        .onVisibleAreaChanged { visibleArea = $0 }
}

Authentication

See the Migrate authentication from 100.x to 200.x topic for instructions for migrating authentication code in your app.

Sketching

Interactively drawing geometries on a map view is performed using GeometryEditor instead of AGSSketchEditor.

Other resources

You can check out the new Samples or the Toolkit for more examples.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.