Display a map SwiftUI

View on GitHub
Sample viewer app

Display a map and demonstrate common usecases in SwiftUI.

Image of Display map 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

  1. Create a SwiftUIMapView to wrap an AGSMapView with SwiftUI UIViewRepresentable protocol.
  2. Create a nested Coordinator type to implement AGSGeoViewTouchDelegate methods.
  3. Initialize the SwiftUIMapView with a map and graphics overlays.
  4. Create a DisplayMapSwiftUIView to combine the SwiftUIMapView together with other view components, automatically preview in Xcode, and provide useful context for the map.
  5. Create a UIHostingController to integrate and manage the DisplayMapSwiftUIView 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 via UIViewRepresentable protocol, and using Coordinator 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

SwiftUIMapView.swiftSwiftUIMapView.swiftDisplayMapSwiftUIView.swiftDisplayMapSwiftUIViewController.swift
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
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
// 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)
    }
}

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