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.x | v200.x |
---|---|
AGSMap | Map |
AGSMapView | MapView |
AGSLoadable | Loadable |
AGSViewpoint | Viewpoint |
AGSPoint | Point |
AGSFeatureLayer | FeatureLayer |
AGSArcGISFeatureTable | ArcGISFeatureTable |
The word Runtime
has been dropped from type names as part of product renaming to ArcGIS Maps SDK
v100.x | v200.x |
---|---|
ArcGISRuntimeEnvironment | ArcGISEnvironment |
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:
AGSGeometryEngine.bufferGeometry(geometry, byDistance: 5000)
200.x:
GeometryEngine.buffer(around: geometry, distance: 5000)
- Method arguments have been reordered.
100.x:
featureLayer.setFeature(feature, visible: true)
200.x:
featureLayer.setVisible(true, for: feature)
- Boolean properties read as assertions.
100.x:
trackingStatus.calculatingRoute
200.x:
trackingStatus.isCalculatingRoute
- Factory methods use a
make
prefix.
100.x:
featureTable.createFeature()
200.x:
featureTable.makeFeature()
- Classes that contain collections provide mutating methods to modify them.
100.x:
graphicsOverlay.graphics.add(graphic)
200.x:
graphicsOverlay.addGraphic(graphic)
Swift Structured Concurrency Related Changes
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:
routeTask.solveRoute(with: routeParameters) { (routeResult: AGSRouteResult?, error: Error?) in
if let error {
// Handle error.
} else if let routeResult {
// Use the result.
}
}
200.x:
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:
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:
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:
locationDisplay.dataSourceStatusChangedHandler = { [weak self] isStarted in
// Check if started.
}
200.x:
for await isStarted in locationDisplay.statusChanged {
// Check if started.
}
Specifically, for delegate methods, such as in route tracker provided by AGS
, the new matching pattern is to use an asynchronous stream.
100.x:
extension NavigateRouteViewController: AGSRouteTrackerDelegate {
func routeTracker(_ routeTracker: AGSRouteTracker, didUpdate trackingStatus: AGSTrackingStatus) {
routeRemainingGraphic.geometry = trackingStatus.routeProgress.remainingGeometry
routeTraversedGraphic.geometry = trackingStatus.routeProgress.traversedGeometry
}
}
200.x:
for try await newStatus in routeTracker.$trackingStatus {
routeRemainingGraphic.geometry = newStatus?.routeProgress.remainingGeometry
routeTraversedGraphic.geometry = newStatus?.routeProgress.traversedGeometry
}
SwiftUI Related Changes
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 UI
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:
@IBOutlet private var mapView: AGSMapView! {
didSet {
mapView.map = AGSMap(basemapStyle: .arcGISOceans)
mapView.setViewpoint(AGSViewpoint(latitude: -117, longitude: 34, scale: 1e5))
}
}
200.x:
// 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 Map
and Scene
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.
.onViewpointChanged(kind: .centerAndScale) { viewpoint = $0 }
- DrawStatus: Perform actions when the map view draw status changes.
.onDrawStatusChanged {
if $0 == .completed { print("Map view draw completed.") }
}
- LocationDisplay: Turn on and configure the location display in the map view.
.locationDisplay(model.locationDisplay)
- Callout: Display a callout with the coordinates of the tapped point in the map view.
The gesture delegate methods in AGS
are now replaced by various view modifiers.
.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 Map
to allow operations such as identify to be performed.
The following code shows the difference between identifying using AGS
in v100.15 vs. using Map
in v200.x.
100.x:
// 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:
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 @ State Object
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:
@IBOutlet private var mapView: AGSMapView! {
didSet {
mapView.map = AGSMap(basemapStyle: .arcGISOceans)
}
}
// Get the visible area using…
private var visibleArea: AGSPolygon? {
mapView.visibleArea
}
200.x:
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 Geometry
instead of AGS
.
Other resources
You can check out the new Samples or the Toolkit for more examples.