SwiftUI · · 5 min read

Working with Maps and Annotations in SwiftUI

Working with Maps and Annotations in SwiftUI

MapKit is a powerful framework that allows developers to add maps, annotations, and location-based features to their iOS applications. With SwiftUI, you can easily integrate MapKit into your app and create interactive and dynamic maps that offer a great user experience. In this tutorial, we will explore how to work with maps and annotations in SwiftUI, and how to customize the map style and camera position.

The MapKit Basics

Let’s start with the basics of MapKit. The MapKit framework includes a Map view that developers can use to embed a map in any SwiftUI project. Here is an example:

import SwiftUI
import MapKit

struct ContentView: View {
  var body: some View {
    Map()
  }
}

Before using the Map view, you have to import the MapKit framework. Then, to create a map, simply instantiate a Map view. If you’ve opened the Preview canvas in Xcode, you should see a full screen map in the simulator.

swiftui-maps-basics

Changing the Initial Position with Map Camera

Instead of displaying a default location, the Map view has another init method for you to change the initial position of the map:

init(
    initialPosition: MapCameraPosition,
    bounds: MapCameraBounds? = nil,
    interactionModes: MapInteractionModes = .all,
    scope: Namespace.ID? = nil
) where Content == MapContentView<Never, EmptyMapContent>

You can an instance of MapCameraPosition as the initial position of the map. MapCameraPosition contains various properties that you can use to control which place or region is displayed, including:

  • automatic
  • item(MKMapItem) – for displaying a specific map item.
  • region(MKCoordinateRegion) – for displaying a specific region.
  • rect(MKMapRect) – for displaying specific map boundaries.
  • camera(MapCamera) – for displaying an existing camera position.
  • userLocation() – for displaying the user’s location

For instance, you can instruct the map to display a specific region by using .region(MKCoordinateRegion):

Map(initialPosition: .region(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.

swiftui-maps-initial-position

If you have a particular location for display, you can pass a map item as the initial position. Here is a sample code snippet:

extension CLLocationCoordinate2D {
    static let bigBen = CLLocationCoordinate2D(latitude: 51.500685, longitude: -0.124570)
}

struct ContentView: View {

    var body: some View {
        Map(initialPosition: .item(MKMapItem(placemark: .init(coordinate: .bigBen))))
    }
}

Animating the Change of Map Position

The Map view also provides an additional init method that accepts a binding to MapCameraPosition. If you need to change the position of the map, this init method is more appropriate:

@State private var position: MapCameraPosition = .automatic

Map(position: $position) {
   .
   .
   .
}

For example, if you want to add two buttons for users to switch between two locations, you can write the code like this:

extension CLLocationCoordinate2D {
    static let bigBen = CLLocationCoordinate2D(latitude: 51.500685, longitude: -0.124570)
    static let towerBridge = CLLocationCoordinate2D(latitude: 51.505507, longitude: -0.075402)
}

struct ContentView: View {

    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        Map(position: $position)
            .onAppear {
                position = .item(MKMapItem(placemark: .init(coordinate: .bigBen)))
            }
            .safeAreaInset(edge: .bottom) {
                HStack {
                    Button(action: {
                        withAnimation {
                            position = .item(MKMapItem(placemark: .init(coordinate: .bigBen)))
                        }
                    }) {
                        Text("Big Ben")
                    }
                    .tint(.black)
                    .buttonStyle(.borderedProminent)

                    Button(action: {
                        withAnimation {
                            position = .item(MKMapItem(placemark: .init(coordinate: .towerBridge)))
                        }
                    }) {
                        Text("Tower Bridge")
                    }
                    .tint(.black)
                    .buttonStyle(.borderedProminent)
                }
            }    
    }
}

By wrapping the position variable with withAnimation, the map view will automatically animate the position change.

swiftui-maps-change-position

This animation works even better when you provide a MapCamera with a pitch angle to create a 3D perspective. To see what happens, you can try changing the position of Big Ben in the following line of code:

position = .camera(MapCamera(
                                        centerCoordinate: .bigBen, 
                                        distance: 800, 
                                        heading: 90, 
                                        pitch: 50))

When you preview the map view, the camera angle adjusts to show a 3D perspective of the region.

swiftui-maps-3d-perspective

Adding Markers and Annotations

swiftui-maps-marker-basics

Markers are a useful feature in MapKit that allow you to display content at a specific coordinate on the map. It adds an extra layer of information to your map, such as a store or a restaurant. Markers can be customized with a system image and tint color, making them visually distinct and easy to recognize. Whether you’re building a navigation app or a travel guide, markers are a valuable tool that can help you create a better user experience.

To add a marker, you can create the Marker view in the map content builder closure like this:

Map(position: $position) {
    Marker("Pickup here", coordinate: .pickupLocation)
}

Optionally, you can customize the Marker object with a system image. To change the color of the marker, use the tint modifier:

Marker("Pickup here", 
                systemImage: "car.front.waves.up", 
                coordinate: .pickupLocation)
                .tint(.purple)

In addition to Marker, SwiftUI now includes an Annotation view in iOS 17 for indicating a location on a map. It functions similarly to Marker, but offers greater flexibility for customization.

To add an annotation, you create an Annotation view in the map content closure. Here is a sample code snippet for adding a simple annotation:

Map(position: $position) {
    Annotation("Pick up", coordinate: .pickupLocation, anchor: .bottom) {
        Image(systemName: "car.front.waves.up")
    }
}

You have the flexibility to customize the annotation in a variety of ways. By attaching different modifiers to it, you can change its appearance and behavior. Additionally, you can use stack views to arrange the different components of the annotation and create a layout that suits your needs. Here is an example:

Annotation("Pick up", coordinate: .pickupLocation, anchor: .bottom) {
    ZStack {
        Circle()
            .foregroundStyle(.indigo.opacity(0.5))
            .frame(width: 80, height: 80)

        Image(systemName: "car.front.waves.up")
            .symbolEffect(.variableColor)
            .padding()
            .foregroundStyle(.white)
            .background(Color.indigo)
            .clipShape(Circle())
    }
}

This results an animated annotation as shown in the below illustration.

swiftui-maps-annotation-animated

Changing the Map Style

By default, the map view renders the map in a standard style. However, you can change the style by using the mapStyle modifier:

Map {

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

This creates a map style based on satellite imagery. By specifying a realistic elevation, the map view renders a 3D map with a realistic appearance.

swiftui-maps-realistic

Optionally, you can also change the map style to hybrid like this:

.mapStyle(.hybrid)

Summary

This tutorial covers how to work with maps and annotations in SwiftUI, using the MapKit framework. The latest version of SwiftUI offers additional APIs and views for developers to further customize the map view. By now, you should know how to embed a map in your app and add an annotation to highlight a location on the map.

If you want to learn more about SwiftUI, don’t forget to check out our Mastering SwiftUI book.

Read next