SwiftUI · · 4 min read

How to Work with SwiftUI Maps and Annotations

How to Work with SwiftUI Maps and Annotations

When the SwiftUI framework was first released, developers are required to wrap the MKMapView class in order to embed a map in a SwiftUI application. With the release of Xcode 12, the latest version of SwiftUI provides a native SwiftUI Map view for you to display a map interface . Optionally, you can display annotations using the built-in annotation views such as MapMarker.

In this tutorial, I will show you how to use the Map structure in SwiftUI and create annotations at a specific locations on the map.

Displaying a Map View in SwiftUI

By referring the documentation of Map, you should find the following init method of the structure:

init(coordinateRegion: Binding<MKCoordinateRegion>, interactionModes: MapInteractionModes = .all, showsUserLocation: Bool = false, userTrackingMode: Binding<MapUserTrackingMode>? = nil) where Content == _DefaultMapContent

To work with Map, you need to provide a binding of MKCoordinateRegion that keeps track of the region to display on the map. The MKCoordinateRegion structure lets you specify a rectangular geographic region centered around a specific latitude and longitude.

Here is an example:

MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40.75773, longitude: -73.985708), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))

The coordinates in the above sample is the GPS coordinates of Times Square in New York. The value of span is used to define your desired zoom level of the map. The smaller the value, the higher is the zoom level.

To embed a map in SwiftUI, you first need to import the MapKit framework:

import MapKit

And then declare a state variable to keep track of the map region like this:

@State private var region: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40.75773, longitude: -73.985708), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))

Lastly, you use the Map structure and pass it with the binding of region:

var body: some View {
    Map(coordinateRegion: $region)
}

The preview should show render a map interface that centered around Times Square in New York.

swiftui-map

If you want to display a full screen map, you can attach the following modifier to Map:

Map(coordinateRegion: $region)
    .edgesIgnoringSafeArea(.all)

The init method provides several optional parameters. By default, the map allows users to pan and zoom. If you want to disable user interactions of the map, you can pass an empty array for the interactionModes parameter like this:

Map(coordinateRegion: $region, interactionModes: [])

To display the user’s location on the map, you can set the value of showsUserLocation to true:

Map(coordinateRegion: $region, interactionModes: [], showsUserLocation: true)

The map even lets you follow the user’s location by passing a binding of MapUserTrackingMode like this:

Map(coordinateRegion: $region, interactionModes: [], showsUserLocation: true, userTrackingMode: .constant(.follow))

Displaying an Annotation on Maps

Let’s read the API documentation of Map. You will find another initializer for displaying a map with annotations:

init<Items, Annotation>(coordinateRegion: Binding<MKCoordinateRegion>, interactionModes: MapInteractionModes = .all, showsUserLocation: Bool = false, userTrackingMode: Binding<MapUserTrackingMode>? = nil, annotationItems: Items, annotationContent: @escaping (Items.Element) -> Annotation) where Content == _DefaultAnnotatedMapContent<Items>, Items : RandomAccessCollection, Annotation : MapAnnotationProtocol, Items.Element : Identifiable

It’s very similar to the previous initializer but this one accepts a collection of annotations that each annotation should conform to the Identifiable protocol.

Now let’s first define a structure for the annotation item:

struct AnnotatedItem: Identifiable {
    let id = UUID()
    var name: String
    var coordinate: CLLocationCoordinate2D
}

This structure is used to hold the name and the coordinate of the annotation. Most importantly, it conforms to the Identifiable protocol.

As an example, we define an array of point of interests in ContentView:

private var pointsOfInterest = [
    AnnotatedItem(name: "Times Square", coordinate: .init(latitude: 40.75773, longitude: -73.985708)),
    AnnotatedItem(name: "Flatiron Building", coordinate: .init(latitude: 40.741112, longitude: -73.989723)),
    AnnotatedItem(name: "Empire State Building", coordinate: .init(latitude: 40.748817, longitude: -73.985428))
    ]

To display the annotations, you just need to specify the array when instantiating the map like this:

Map(coordinateRegion: $region, annotationItems: pointsOfInterest) { item in
    MapMarker(coordinate: item.coordinate, tint: .red)
}
.edgesIgnoringSafeArea(.all)

MapMarker is one of the built-in annotation views that marks a specific location using a balloon-shaped. Alternatively, you can use MapPin to pinpoint the location.

swiftui-map-annotations

You can even create your custom annotation by using MapAnnotation. Here is an example:

MapAnnotation(coordinate: item.coordinate) {
    RoundedRectangle(cornerRadius: 5.0)
        .stroke(Color.purple, lineWidth: 4.0)
        .frame(width: 30, height: 30)
}

The annotations appear as a rounded rectangle. The possibilities are endless. You are free to create your own annotation view.

Where to go from here

It’s great to see Apple to bring a native Map view to SwiftUI. I hope you now understand how to embed a map interface in your iOS app. If you want to dive deeper into the SwiftUI framework, you can further check out our Mastering SwiftUI book.

Read next