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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
New SwiftUI Support for MapKit in Xcode 15
5 mins
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:
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
.
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))
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)
}
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:
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
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))
Shift-Option-drag to move the map camera angle:
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)) {
Now, you can take an aerial tour around Honolulu by changing the artData
item in the preview. For example, artData[12]
:
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
andAnnotation
structures. - The new
MapStyle
structure. - The new
MapCamera
structure.
Meet MapKit for SwiftUI shows off several nifty features, including:
-
onMapCameraChange(frequency:)
, an instance method ofMapPitchButton
. -
MapPolyline
andMKRoute
. -
MapCompass
andMapScaleView
.
We hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!