New SwiftUI Support for MapKit in Xcode 15

At WWDC 2023, Apple announced big improvements to SwiftUI support for MapKit. Learn all about your new options for maps, markers, annotations, style and camera position. By Audrey Tam.

Leave a rating/review
Download materials
Save for later
Share

At WWDC 2023, Apple announced better SwiftUI support for MapKit. MapKit is a huge API, so it’ll be a while before we see a fully native SwiftUI version. But it’s definitely getting easier to display an interactive map view in your app. The most widely-used feature — annotations — gets a big improvement, and the new map style modifier requires almost no work to add a ton of pizzazz to your app. Slowly but surely, MapKit is acquiring a more SwiftUI “touch and feel”.

On the downside, if you’ve written a lot of Map code in the past couple of years, you might need to rewrite some of it, as Xcode 15 replaces all the Xcode 12 Map initializers.

Getting Started

Click Download materials at the top or bottom of this article to download the starter project. Open it in Xcode 15 beta to see what you have to work with.

PublicArt

I wrote the first version of PublicArt in 2014 for my very first raywenderlich.com tutorial MapKit Tutorial: Getting Started. It was an update of Ray’s original MapKit tutorial, and Andrew Tetlaw updated it again in 2020.

In 2019, I adapted PublicArt for SwiftUI Tutorial: Navigation. Using Xcode 11, I had to create a struct MapView: UIViewRepresentable to display an MKMapView in a SwiftUI app.

Earlier this year, Josh Steele updated PublicArt to Xcode 14.2 for our SwiftUI Fundamentals course. This is the starter project for this article: It uses the SwiftUI Map introduced in Xcode 12.

For this article, the starter project has iOS Deployment set to iOS 17.

Xcode 12 Map

Open LocationMap in the code editor. There’s an MKCoordinateRegion @State property. You pass a binding to this into the Map initializer, along with an array of annotationItems, then display a MapMarker using an artwork item’s coordinate property. When the Map view appears, you set region.center and region.span:

@State var region = MKCoordinateRegion()
  ...
Map(coordinateRegion: $region, annotationItems: [artwork]) { artwork in
  MapMarker(coordinate: artwork.coordinate)
}
.onAppear {
  region.center = artwork.coordinate
  region.span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
}

Refresh the preview or build and run the app, and navigate to the map:

Maps view of Kuhio Beach

Xcode 15 Beta Map

This year’s Map deprecates all the Xcode 12 initializers. Instead of bindings to MKCoordinateRegion or MKMapRect, you create a Map with MapCameraBounds or MapCameraPosition.

You can create a MapCameraBounds value with an MKCoordinateRegion or MKMapRect value, plus minimum and maximum distances, or you can create one using only minimum and maximum distances.

Or you can create a Map using no parameters at all!

Map & Marker

In LocationMap, comment out @State var region and replace all the Map code — Map(...) { ... }.onAppear { ... } — with this:

Map {
  Marker(artwork.title, coordinate: artwork.coordinate)
}

MapMarker is deprecated, replaced by Marker, where you display a label, which is a View.

Maps zoomed in to marker

You get the same balloon marker, but the map has zoomed right in to it. To show approximately the same region as the old region, initialize Map with bounds:

Map(bounds:
      MapCameraBounds(minimumDistance: 4500,
                      maximumDistance: 4500))

Maps zoomed out from marker

Annotation

In addition to Marker, there’s a new Annotation structure:

Annotation(artwork.title,
           coordinate: artwork.coordinate) {
  Image(systemName: "person.bust")
    .padding(6)
    .foregroundStyle(.white)
    .background(Color.blue)
}

Annotation on Maps marker

An Annotation displays both a label View and a content View, so you can customize your map pins. For example, add these properties to Artwork:

var symbol: String {
  switch discipline {
  case "Monument", "Sculpture":
    return "person.bust"
  case "Mural":
    return "paintpalette"
  case "Plaque":
    return "person.text.rectangle"
  default:
    return "mappin"
  }
}

var background: Color {
  switch discipline {
  case "Monument", "Sculpture":
    return .blue
  case "Mural":
    return .mint
  case "Plaque":
    return .orange
  default:
    return .red
  }
}

Each artwork has a discipline property, and these new properties specify symbols and background colors for the most-populated disciplines.

Now, replace your Marker and Annotation code with:

Marker(artwork.title, systemImage: artwork.symbol,
       coordinate: artwork.coordinate)
.tint(artwork.background)

Annotation(artwork.title,
           coordinate: artwork.coordinate,
           anchor: .topLeading) {
  Image(systemName: artwork.symbol)
    .padding(6)
    .foregroundStyle(.white)
    .background(artwork.background)
}

Like the deprecated MapMarker, Marker lets you specify tint.

To see these custom symbols and colors at work, change the index of the artData item in the preview to 1. This artwork is a mural, so you get mint-colored pins, and the annotation symbol is a paint palette:

Marker with paint palette annotation

You probably wouldn’t want to use both Marker and Annotation for the same location, but while they’re both there, see how you can adjust the position of the annotation’s anchor:

Annotation(artwork.title,
           coordinate: artwork.coordinate,
           anchor: .topLeading)  // add this argument

Marker and annotation, both with paint palette

The value of anchor can be top, bottom, leading, trailing or combinations, or you can specify a CGPoint with x and y coordinates.

Map Style

And now for one of the best new features: mapStyle. Add this modifier to Map after its closing brace:

.mapStyle(.imagery(elevation: .realistic))

And change bounds to zoom in:

Map(bounds:
      MapCameraBounds(minimumDistance: 1500,
                      maximumDistance: 1500))

Top-down satellite view of marker location

Shift-Option-drag to move the map camera angle:

Angled satellite view of marker location

You can also add an initialPosition parameter to Map to set the map camera:

Map(
  initialPosition: .camera(MapCamera(
    centerCoordinate: artwork.coordinate,
    distance: 1200,
    heading: 90,
    pitch: 60)),
  bounds:
    MapCameraBounds(minimumDistance: 1500,
                    maximumDistance: 1500)) {

Rotated satellite view of marker location

Now, you can take an aerial tour around Honolulu by changing the artData item in the preview. For example, artData[12]:

Satellite view of golf course

There are only 17 artworks, so don’t go beyond artData[16].

Where to Go From Here?

Download the final project using Download materials at the top or bottom of this article.

In this article, you learned about:

  • The new way to create a Map.
  • The new Marker and Annotation structures.
  • The new MapStyle structure.
  • The new MapCamera structure.

Meet MapKit for SwiftUI shows off several nifty features, including:

  • onMapCameraChange(frequency:), an instance method of MapPitchButton.
  • MapPolyline and MKRoute.
  • MapCompass and MapScaleView.

We hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!