MapKit for SwiftUI

In iOS 17, Apple introduced a more SwiftUI friendly API for MapKit.

MapKit Deprecations

Once you update your App’s target to iOS 17, Xcode marks any usage of the old Map initializers as deprecated:

init coordinate region was deprecated in iOS 17. Use map initializers that take a map content builder instead

MapKit for SwiftUI in iOS 17 switches to map initializers that take a MapContentBuilder. Here’s a quick introduction to the basics:

MapContentBuilder (iOS 17)

In iOS 17, the various initializers for a map view expect a content parameter that is a MapContentBuilder. This is a result builder that lets you add map content such as markers, annotations, and custom content in the closure.

To see how this works, here’s some coordinates of some London landmarks:

extension CLLocationCoordinate2D {
  static let towerBridge = CLLocationCoordinate2D(latitude: 51.5055, longitude: -0.075406)
  static let boe = CLLocationCoordinate2D(latitude: 51.5142, longitude: -0.0885)
  static let hydepark = CLLocationCoordinate2D(latitude: 51.508611, longitude: -0.163611)
  static let kingsCross = CLLocationCoordinate2D(latitude: 51.5309, longitude: -0.1233)
}

To create a map view with some markers and an annotation:

struct ContentView: View {
  var body: some View {
    Map {
      Marker("Tower Bridge", coordinate: .towerBridge)
      Marker("Hyde Park", coordinate: .hydepark)
      Marker("Bank of England", 
        systemImage: "sterlingsign", coordinate: .boe)
        .tint(.green)
    
      Annotation("Kings Cross", 
        coordinate: .kingsCross, anchor: .bottom) {
          VStack {
              Text("Get the train here!")
              Image(systemName: "train.side.front.car")
          }
          .foregroundColor(.blue)
          .padding()
          .background(in: .capsule)
      }
    }
  }
}

With no other options the map view bounds enclose the map content:

Map View of central London

Map Interaction

To control how a user can interact with the map pass a set of the allowed modes. The default is to allow all (pan, zoom, pitch, rotate):

Map(interactionModes: [.pan,.pitch]) { ... }

Map Style

The Map Style view modifier lets you switch between standard, satellite or hybrid styles, control the elevation, show points of interest, and show traffic:

Map { ...
}
.mapStyle(.hybrid(elevation: .realistic,
  pointsOfInterest: .including([.publicTransport]), 
  showsTraffic: true))

Map Controls

The standard map controls such as the compass, user location, pitch, scale, and zoom controls are all implemented as SwiftUI views. This means you can place them anywhere in your view, though you need to define a map scope namespace to associate them with the map they control:

struct ContentView: View {
  @Namespace var mapScope

  var body: some View {
    VStack {
      Map(scope: mapScope) { ... }
      MapCompass(scope: mapScope)
    }
    .mapScope(mapScope)
  }
}

Otherwise to use them in their standard positions use the map control view modifier:

Map { ...
}
.mapControls {
  MapPitchToggle()
  MapUserLocationButton()
  MapCompass()
}

Map Camera Position

The map camera position defines a virtual position above the map surface that you view the map from. You can use existing map items, a map bounds, region or user location to create a map camera position and set the initial map position:

Map(initialPosition: position)

Map view showing UK with pin on Tower Bridge

Pass the map a binding to a MapCameraPosition to have it track the camera position as the user moves around the map:

struct ContentView: View {
  @State private var position: MapCameraPosition = .region(.uk)

  var body: some View {
    Map(position: $position) {
      Marker("Tower Bridge", coordinate: .towerBridge)
    }
  }
}

Setting the position causes the map to change it’s camera position to match. For example, to add a toolbar button that resets the map after the user has moved position:

Map(position: $position) { ...
}
.toolbar {
  ToolbarItem {
    Button("Reset") {
      position = .region(.uk)
    }
  }
}

Setting the position to .automatic makes the map frame the content.

Learn More

There’s a lot more to MapKit for SwiftUI. I recommend watching the WWDC23 session: