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:

1
AGSGeometryEngine.bufferGeometry(geometry, byDistance: 5000)

200.x:

1
GeometryEngine.buffer(around: geometry, distance: 5000)
  • Method arguments have been reordered.

100.x:

1
featureLayer.setFeature(feature, visible: true)

200.x:

1
featureLayer.setVisible(true, for: feature)
  • Boolean properties read as assertions.

100.x:

1
trackingStatus.calculatingRoute

200.x:

1
trackingStatus.isCalculatingRoute
  • Factory methods use a make prefix.

100.x:

1
featureTable.createFeature()

200.x:

1
featureTable.makeFeature()
  • Classes that contain collections provide mutating methods to modify them.

100.x:

1
graphicsOverlay.graphics.add(graphic)

200.x:

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:

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:

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:

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:

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:

1
2
3
locationDisplay.dataSourceStatusChangedHandler = { [weak self] isStarted in
    // Check if started.
}

200.x:

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:

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:

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:

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:

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.
1
.onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
  • DrawStatus: Perform actions when the map view draw status changes.
1
2
3
.onDrawStatusChanged {
    if $0 == .completed { print("Map view draw completed.") }
}
  • LocationDisplay: Turn on and configure the location display in the map view.
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.

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:

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:

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:

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:

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.

You can no longer sign into this site. Go to your ArcGIS portal or the ArcGIS Location Platform dashboard to perform management tasks.

Your ArcGIS portal

Create, manage, and access API keys and OAuth 2.0 developer credentials, hosted layers, and data services.

Your ArcGIS Location Platform dashboard

Manage billing, monitor service usage, and access additional resources.

Learn more about these changes in the What's new in Esri Developers June 2024 blog post.

Close