Change a map viewpoint with MapKit

Change a map viewpoint with MapKit

Discover how to change a MapKit map's camera position within a SwiftUI app.

Interactive maps are a key part of many travel, delivery, or navigation apps, where there's often the need to display geographical data. With the introduction of iOS 17, SwiftUI gained a new MapCamera API, as part of the MapKit framework, designed to give developers full programmatic control over the map camera.

In this article, we’ll walk through how to use MapCamera and MapCameraPosition in SwiftUI. To illustrate it, we will create a simple map experience where the user can explore various cities worldwide by long-pressing on the map, getting a random town every time.

import SwiftUI
import MapKit

struct MapLocation: Identifiable, Equatable {
    let id = UUID()
    let name: String
    let coordinate: CLLocationCoordinate2D
    
    static func == (lhs: MapLocation, rhs: MapLocation) -> Bool {
        lhs.id == rhs.id
    }
}

enum City: CaseIterable {
    
    case paris
    case newYork
    case tokyo
    case naples
    case berlin
    
    var mapLocation: MapLocation {
        var name: String
        var coordinate: CLLocationCoordinate2D
        
        switch self {
        case .paris:
            name = "Paris"
            coordinate = CLLocationCoordinate2D(latitude: 48.858844, longitude: 2.294351)
        case .newYork:
            name = "New York"
            coordinate = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060)
        case .tokyo:
            name = "Tokyo"
            coordinate = CLLocationCoordinate2D(latitude: 35.6895, longitude: 139.6917)
        case .naples:
            name = "Naples"
            coordinate = CLLocationCoordinate2D(latitude: 40.841235, longitude: 14.2551)
        case .berlin:
            name = "Berlin"
            coordinate = CLLocationCoordinate2D(latitude: 52.520008, longitude: 13.40495)
        }
        
        return MapLocation(name: name, coordinate: coordinate)
    }
}

struct ContentView: View {
    
    @State private var selectedLocation: MapLocation = City.paris.mapLocation
    @State private var isPresented = false
    
    @State private var cameraPosition: MapCameraPosition = .camera(
        MapCamera(
            centerCoordinate: City.tokyo.mapLocation.coordinate,
            distance: 2000,
            heading: 0,
            pitch: 45
        )
    )
    
    var body: some View {
        VStack {
            Map(position: $cameraPosition) {
                ForEach(City.allCases.map(\.mapLocation)) { mapLocation in
                    Marker(mapLocation.name, coordinate: mapLocation.coordinate)
                }
            }
            .mapStyle(.standard)
            // Long press gesture changes the selected location randomly
            .onLongPressGesture {
                selectedLocation = City.allCases.randomElement()!.mapLocation
            }
            // Once the selected location changes, updates the map camera position
            .onChange(of: selectedLocation) { _, newLocation in
                cameraPosition = MapCameraPosition.camera(
                    MapCamera(
                        centerCoordinate: newLocation.coordinate,
                        distance: 2000,
                        heading: 0,
                        pitch: 45
                    )
                )
            }
        }
    }
}

In the example above, the cameraPosition property is used to precisely control the viewpoint of the Map View, representing the state of the map’s camera. By utilizing the camera(_:) method, which requires a parameter of the MapCamera type, we can define the camera’s center coordinate, distance from the ground, heading, and pitch.

0:00
/0:22

Initially centered on Tokyo, the camera dynamically updates when a new city is selected, triggered by a long-press gesture, updating the cameraPosition property with a MapCamera value.

This is one of the different possibilities that the MapCameraPosition object offers to set the viewpoint of the map, according to our requirements. Here’s the complete list of these methods:

  • item(_:allowsAutomaticPitch:) : creates a new camera position centered on a map item and automatically selects the pitch you provide.
  • rect(_:) : creates a new camera position based on the map boundaries you specify..
  • region(_:) : creates a new camera position based on the coordinate region you provide.
  • userLocation(followsHeading:fallback:) : creates a camera position with a specific fallback position and optionally tracks the user’s heading.