Display a map and demonstrate common usecases in SwiftUI.
Use case
The map is the fundamental building block of any GIS application and is used to specify how geographic data is organized and communicated to your users. With the introduction of SwiftUI in iOS 13, it's worth exploring the interoperability of SwiftUI and current UIKit-based Runtime SDK, while waiting for the new Runtime SDK in Swift.
How to use the sample
Run the sample to view the map. Single tap to add a circle marker to the map. Tap "Choose Map" button to change the map. Tap "Clear Graphics" button to clear all added graphics.
How it works
- Create a
SwiftUIMapView
to wrap anAGSMapView
with SwiftUIUIViewRepresentable
protocol. - Create a nested
Coordinator
type to implementAGSGeoViewTouchDelegate
methods. - Initialize the
SwiftUIMapView
with a map and graphics overlays. - Create a
DisplayMapSwiftUIView
to combine theSwiftUIMapView
together with other view components, automatically preview in Xcode, and provide useful context for the map. - Create a
UIHostingController
to integrate and manage theDisplayMapSwiftUIView
into UIKit view hierarchy.
Relevant API
- AGSBasemapStyle
- AGSMap
- AGSMapView
Additional information
This sample demonstrates how to use AGSMapView
in SwiftUI. It features the following:
- Using SwiftUI together with storyboard via
UIHostingController
- Embedding a
UIView
in a SwiftUI view viaUIViewRepresentable
protocol, and usingCoordinator
to translate Cocoa delegate methods into SwiftUI view actions - Common usecases of a map: adding graphics to a map view; changing the map displayed by a map view; responding to tap events on a map view
Tags
basemap style, interface, interoperability, map, SwiftUI
Sample Code
// Copyright 2021 Esri
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import SwiftUI
import ArcGIS
struct SwiftUIMapView {
let map: AGSMap
let graphicsOverlays: [AGSGraphicsOverlay]
init(
map: AGSMap,
graphicsOverlays: [AGSGraphicsOverlay] = []
) {
self.map = map
self.graphicsOverlays = graphicsOverlays
}
private var onSingleTapAction: ((CGPoint, AGSPoint) -> Void)?
}
extension SwiftUIMapView {
/// Sets a closure to perform when a single tap occurs on the map view.
/// - Parameter action: The closure to perform upon single tap.
func onSingleTap(perform action: @escaping (CGPoint, AGSPoint) -> Void) -> Self {
var copy = self
copy.onSingleTapAction = action
return copy
}
}
extension SwiftUIMapView: UIViewRepresentable {
typealias UIViewType = AGSMapView
func makeCoordinator() -> Coordinator {
Coordinator(
onSingleTapAction: onSingleTapAction
)
}
func makeUIView(context: Context) -> AGSMapView {
let uiView = AGSMapView()
uiView.map = map
uiView.graphicsOverlays.setArray(graphicsOverlays)
uiView.touchDelegate = context.coordinator
return uiView
}
func updateUIView(_ uiView: AGSMapView, context: Context) {
if map != uiView.map {
uiView.map = map
}
if graphicsOverlays != uiView.graphicsOverlays as? [AGSGraphicsOverlay] {
uiView.graphicsOverlays.setArray(graphicsOverlays)
}
context.coordinator.onSingleTapAction = onSingleTapAction
}
}
extension SwiftUIMapView {
class Coordinator: NSObject {
var onSingleTapAction: ((CGPoint, AGSPoint) -> Void)?
init(
onSingleTapAction: ((CGPoint, AGSPoint) -> Void)?
) {
self.onSingleTapAction = onSingleTapAction
}
}
}
extension SwiftUIMapView.Coordinator: AGSGeoViewTouchDelegate {
func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
onSingleTapAction?(screenPoint, mapPoint)
}
}