Using expanded SwiftUI support for MapKit

Using expanded SwiftUI support for MapKit

Learn how to use SwiftUI to integrate Maps in your apps with extended MapKit features like annotations, overlays, camera controls, and more.

The MapKit implementation for SwiftUI has been somewhat limited to basic features. More advanced implementations, like adding annotations, required the use of UIKit. At WWDC 2023, Apple announced the extension of MapKit features for SwiftUI, enabling the design of more expressive and interactive maps using Views and View Modifiers.

Here are some highlights on what is possible to achieve using MapKit and SwiftUI together on iOS 17.

⚠️
With the release of its new features for MapKit for SwiftUI at WWDC 2023, Apple is deprecating previous symbols and their implementations. Beyond some Map initializers, the previous structures MapAnnotation, MapMarker and MapPin will no longer be used and have been replaced with more powerful annotations and overlays.

Check the Developer Documentation for Map protocols and view modifiers that are no longer supported.

Creating a Map

To create a view that presents a map in your application you just need to use the Map view.

import SwiftUI
import MapKit

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

Changing a Map Style

MapKit comes with multiple map styles that can be configured using the .mapStyle view modifier. Beyond the standard style, maps can be rendered using satellite images with the imagery style. To use satellite images as well as paths of roads and their names, the hybrid style can be used.

import SwiftUI
import MapKit

struct ContentView: View {
    
    let garage = CLLocationCoordinate2D(latitude: 40.83657722488077,
                                        longitude: 14.306896671048852)
    
    var body: some View {
        Map {
            Marker("Garage", coordinate: garage)
        }
        .mapStyle(.standard(elevation: .realistic))
    }
}

Changing the Map Camera

The camera position can be defined automatically or be initialized with an MKCoordinateRegion, defined by center coordinates and span to define the zoom level, an MKMapRect, defined by the corner coordinates of map section, as well as an MKMapItem that would be placed at the center of the camera view.

import SwiftUI
import MapKit

struct ContentView: View {
    
    let garage = CLLocationCoordinate2D(latitude: 40.83657722488077,
                                        longitude: 14.306896671048852)
    
	let regionCenter = CLLocationCoordinate2D(latitude: 40.83657722488077, longitude: 14.306896671048852)
	let regionSpan = MKCoordinateSpan(latitudeDelta: 0.125, longitudeDelta: 0.125)
    
    @State private var position: MapCameraPosition = .region(MKCoordinateRegion(center: regionCenter, span: regionSpan))
    
    var body: some View {
        Map(position: $position) {
            Marker("Garage", coordinate: garage)
        }
        .mapStyle(.standard(elevation: .realistic))
    }
}

Showing the User Annotation

You can use the UserAnnotation view to display the user's current location on the map. It can be styled in various ways and is making use of the UserLocation structure that contains information about the current location of the user.

import SwiftUI
import MapKit

struct ContentViews: View {
    
    let garage = CLLocationCoordinate2D(latitude: 40.83657722488077,
                                        longitude: 14.306896671048852)
    
    @State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
    
    var body: some View {
        Map(position: $position) {
            Marker("Garage", coordinate: garage)
            UserAnnotation()
        }
        .mapStyle(.standard(elevation: .realistic))
    }
}

Adding Controls to a Map

MapKit features a number of controls that allow the user to interact with the map in an easy way.

  • MapCompass displays the current orientation of the associated map.
  • MapPitchButton sets the camera angle to a viewable angle or returns to flat.
  • MapPitchSlider allows the user to change the pitch of the map.
  • MapScaleView shows a legend with distance information.
  • MapUserLocationButton frames the map to the user's current location.
  • MapZoomStepper allows the user to adjust the zoom level. All of these controls can be added using the .mapControls modifier now.
import SwiftUI
import MapKit

struct ContentViews: View {
    
    let garage = CLLocationCoordinate2D(latitude: 40.83657722488077,
                                        longitude: 14.306896671048852)
    
    @State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
    
    var body: some View {
        Map(position: $position) {
            Marker("Garage", coordinate: garage)
        }
        .mapStyle(.standard(elevation: .realistic))
        .mapControls {
            MapUserLocationButton()
            MapCompass()
            MapScaleView()
        }
    }
}

Adding Markers to a Map

A Marker is a balloon-shaped annotation marking a map location. To initialise one you need to give it coordinates and a label, or you can use a MKMapItem as well. They can further be customized with custom images, or monograms to stand out. Here are some examples of how you can create markers:

import SwiftUI
import MapKit

struct ContentView: View {
    
    var body: some View {
        Map{
           Marker("Garage",
           		  systemImage:"figure.wave",
                  coordinate: CLLocationCoordinate2D(latitude: 40.836, longitude: 14.306)
                  
           Marker("Garage",
	              image:"ImageAsset",
                  coordinate: CLLocationCoordinate2D(latitude: 40.856, longitude: 14.316)
           
           Marker("Garage",
                  monogram:"PAR",
                  coordinate: CLLocationCoordinate2D(latitude: 40.826, longitude: 14.326)
        }
    }
    
}

Adding Annotations to a Map

If you need a more custom annotation you can be created one with the Annotation structure. It needs a label and a coordinate, but then it can be customized with any SwiftUI view.

Here for example, we added a ZStack of two RoundedRectangle as well as an Image to create a custom icon for our garage. You can also hide the annotation titles using the .annotationsTitles(.hidden) modifier.

import SwiftUI
import MapKit

struct ContentView: View {
    
    let garage = CLLocationCoordinate2D(latitude: 40.83657722488077,
                                        longitude: 14.306896671048852)
    
    var body: some View {
        Map {
            Annotation(
                "Garage",
                coordinate: garage
            ){
                ZStack {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.background)
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(.secondary, lineWidth: 5)
                    Image(systemName: "car")
                        .padding(8)
                }
            }
            .annotationTitles(.hidden)
        }
    }
}

There is much more!

We are just scratching the surface of what’s possible to do with MapKit and SwiftUI together with the new updates. There are new view and modifiers that allow you to:

  • Add overlays to a map
  • Search for points of interest
  • Select markers
  • Calculate travel time and get directions
  • Use look around previews

Of course, MapKit still works great with UIKit and there are many more features to bring to SwiftUI. Yet, all the new functionality is super simple to implement and makes MapKit much more accessible within a SwiftUI only approach.

For a deeper dive, have a look at the official developer documentation. The MapKit framework provides powerful ways to give your app a sense of place with maps and location information. Beyond support for SwiftUI, AppKit, and UIKIt, there is also MapKit JS to embed interactive Apple Maps in websites.