Manage GeoView state

SwiftUI is a declarative, state-driven framework that requires you to manage any maps, scenes, and associated data that your map view or scene view needs. You can preserve the state of properties within your view by wrapping value types as State properties. Alternatively, you can manage and preserve state using a model in your app to separate the application's properties and business logic from the UI.

State properties

ArcGIS Maps SDK for Swift allows you to display and interact with map and scene data using the MapView and SceneView classes, respectively. These classes conform to Apple's SwiftUI View and describe what to display (declarative). The view is constantly recomputed as its properties are updated. However, everytime the view or its super view changes, we do not want the view's map or scene recreated. You can use the @State property wrapper to ensure that SwiftUI maintains these properties.

The code below displays a topological-styled basemap within a map view. The @State property wrapper is added to the map property inside the View struct as follows:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
import ArcGIS

struct ContentView: View {

    // Create a map property using the State wrapper
    @State private var map = Map(basemapStyle: .arcGISTopographic)

    var body: some View {
        MapView(map: map)
    }
}

If you are creating a simple mapping application that contains just a few properties in the View, you can implement all of them using just the @State wrapper, as shown above. For more information about preserving state see Apple's Managing user interface state.

Model

If you are working with a more complex application, you may want to preserve the state of many different Swift Maps SDK types, such as Map, Scene, Viewpoint, and GraphicsOverlay. In this situation, the View can get inflated with the many @State properties. To avoid this problem, you can use a model that's separate from the view to store, process, and test these data types. Define a model class that conforms to the ObservableObject protocol and then refer to this model in your view as a StateObject.

Let's see how to implement this. First, create a class called Model that conforms to the ObservableObject protocol. In this case, create the model in an extension to the ContentView. Then, create map as an instance property in the Model class.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
private extension ContentView {

    // Define a class that conforms to the ObservableObject protocol. This is the model.
    class Model: ObservableObject {
        let map: Map

        // Create a new instance of map
        init() {
            map = Map(basemapStyle: .arcGISTopographic)
        }
    }
}

To establish a dependency between the model and the view, instantiate a Model object in the ContentView using the @StateObject property wrapper. The model property now becomes the single source of truth for the view. The @StateObject wrapper ensures that the model property does not get destroyed when the view struct redraws. Finally, initialize a MapView using the model's map.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
struct ContentView: View {

    // Create a model using the StateObject wrapper
    @StateObject private var model = Model()

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

The model gives a cleaner separation of responsibilities between the view and the data. It helps you to organize your code and remove some of the program state and logic from your view structs, effectively separating logic from layout. For more information see Apple's Managing model data in your app.

Examples

Location display example

For a more involved example, let's display your device's current location as a blue dot in the map view. You can achieve this using the Location Display functionality provided with the ArcGIS Maps SDK for Swift. Location display can retrieve the device’s current location from a location data source and display it on top of a map. As the device moves, the map view can respond to new locations by updating the position of the location symbol on the display.

Let's implement this using the model pattern discussed above. In the model, create a new instance of the LocationDisplay, specify its location data source (LocationDataSource), and define functions to start and stop the location data source.

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
private extension ContentView {

    // Define a model that conforms to the ObservableObject protocol.
    class Model: ObservableObject {

        let map: Map

        // The location display.
        let locationDisplay: LocationDisplay

        // Create a new instance of the map and location display.
        init() {
            map = Map(basemapStyle: .arcGISTopographic)
            // Specify that the location data source obtains the device’s location from its operating system.
            locationDisplay = LocationDisplay(dataSource: SystemLocationDataSource())
            locationDisplay.autoPanMode = .recenter
        }

        // Start the location datasource to display the location as a blue dot on the map view.
        func startLocationDataSource() async {
            // Request location permission from the device's user.
            // Set the permission text in info.plist in advance.
            let locationManager = CLLocationManager()
            locationManager.requestWhenInUseAuthorization()
            do {
                try await locationDisplay.dataSource.start()
            } catch {
                print(error.localizedDescription)
            }
        }

        // Stop the location datasource to remove blue dot from the map view.
        func stopLocationDataSource() {
            Task {
                await locationDisplay.dataSource.stop()
            }
        }
    }
}

Next, consume and preserve the state of the model with the @StateObject property wrapper. Initialize the map view with the model's map, and call the map view's locationDisplay view modifier with the model's location display. Call the asynchronous startLocationDataSource function to start the location display data source and display the blue dot on the map view.

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
import SwiftUI
import ArcGIS
import CoreLocation

struct ContentView: View {

    @StateObject private var model = Model()

    var body: some View {
        MapView(map: model.map)
            .locationDisplay(model.locationDisplay)
            .task {
                await model.startLocationDataSource()
            }
            .onDisappear {
                model.stopLocationDataSource()
            }
    }
}

For more details and examples see:

Samples

Find route

Display directions for a route between two points.

Trace utility network

Add a dynamic entity layer

Tutorials

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