WWDC Quick Look 💓 By SwiftGGTeam
Meet MapKit for SwiftUI

Meet MapKit for SwiftUI

Watch original video

Highlight

MapKit engineer Jeff builds a complete Boston trip-planning app from scratch in under 30 minutes, demonstrating how MapKit for SwiftUI dramatically lowers the barrier to map integration through MapContentBuilder closures and declarative APIs.

Core Content

Using maps in SwiftUI used to be painful. The UIKit-era MKMapView required bridging, custom annotations and overlays needed lots of delegate code, and camera control meant manually calculating regions and zoom levels.

(01:42) Jeff’s demo starts from an empty SwiftUI project. Import MapKit, add one line Map(), and an interactive map appears. No delegates, no configuration, no bridging.

(02:41) Annotation content is added through a MapContentBuilder closure—the syntax is almost identical to adding views to a List. Marker shows the standard balloon icon; Annotation can hold any SwiftUI View. The map automatically adjusts its viewport to frame all content—default behavior, no manual calculation needed.

(04:36) Map style switches via the mapStyle modifier. The standard map is flat by default; adding .standard(elevation: .realistic) gives terrain real elevation—bridges and lakes immediately gain depth. You can also switch to satellite or hybrid modes.

(09:10) Camera control uses MapCameraPosition state. automatic lets the map frame content automatically; region shows a specified area; item focuses on a location; userLocation follows the user. After the user manually drags the map, the state becomes positionedByUser—your app can detect this and decide whether to re-center.

(17:33) Selection interaction only requires adding a selection binding to Map. Markers automatically show a selection animation. Combined with onChange, you can fetch routes and show Look Around street-level previews on selection.

(21:54) Overlays are added through the content builder too. MapPolyline shows routes; MapPolygon and MapCircle mark areas. Each overlay can specify aboveRoads or aboveLabels level to control stacking with map labels.

(23:47) Map controls include MapUserLocationButton, MapCompass, and MapScaleView, auto-laid out via the mapControls modifier. You can also place them manually as regular Views—in that case use mapScope to associate with a specific map instance.

Detailed Content

Minimal viable map

import MapKit
import SwiftUI

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

Key points:

  • One line of code gives you an interactive map with pan, zoom, and rotate
  • Automatically handles user location authorization and compass heading

Adding annotations and custom views

struct ContentView: View {
    var body: some View {
        Map {
            // Standard Marker, balloon icon
            Marker("Parking", coordinate: .parkingGarage)
            
            // Custom Annotation, containing any SwiftUI View
            Annotation("Custom Spot", coordinate: .customSpot) {
                ZStack {
                    Circle().fill(.blue)
                    Image(systemName: "car.fill")
                }
            }
        }
    }
}

Key points:

  • Marker suits simple location pins—the system handles icon and color
  • The Annotation closure can contain a full SwiftUI view hierarchy
  • Use anchor: .bottom to align the view’s bottom to the coordinate, not its center

Dynamic annotations from search results

struct ContentView: View {
    @State private var searchResults: [MKMapItem] = []
    @State private var position: MapCameraPosition = .automatic
    
    var body: some View {
        Map(position: $position) {
            ForEach(searchResults, id: \.self) { item in
                Marker(item: item)
            }
        }
        .onChange(of: searchResults) {
            position = .automatic
        }
    }
    
    func search(for query: String) {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.region = visibleRegion
        
        MKLocalSearch(request: request).start { response, _ in
            searchResults = response?.mapItems ?? []
        }
    }
}

Key points:

  • Marker(item:) automatically uses the MKMapItem’s name, icon, and tint
  • Searching for beaches shows umbrella icons; playgrounds show slide icons
  • position = .automatic zooms the map to show all results
  • After the user drags the map, you need to manually reset position to re-frame on new searches

Camera position state

@State private var position: MapCameraPosition = .region(.boston)

// Show a specified region
position = .region(MKCoordinateRegion(
    center: CLLocationCoordinate2D(latitude: 42.36, longitude: -71.06),
    span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
))

// Follow the user location, falling back to a specified region when unauthorized
position = .userLocation(fallback: .region(.boston))

// Show a place and automatically compute a suitable zoom level
position = .item(MKMapItem(placemark: MKPlacemark(coordinate: .beach)))

Key points:

  • MapCameraPosition is a value type, bidirectionally synced with Map via binding
  • After user interaction the state becomes positionedByUser—your app can detect and respond
  • onMapCameraChange gets the current visible region, supporting continuous updates or only on interaction end

Route overlays

Map {
    if let route {
        MapPolyline(route.polyline)
            .stroke(.blue, lineWidth: 5)
    }
}

// Get directions
func getRoute(from source: MKMapItem, to destination: MKMapItem) {
    let request = MKDirections.Request()
    request.source = source
    request.destination = destination
    
    MKDirections(request: request).calculate { response, _ in
        route = response?.routes.first
    }
}

Key points:

  • MapPolyline accepts MKPolyline directly—no coordinate conversion needed
  • StrokeStyle supports dashed lines, gradients, and more
  • MapPolygon and MapCircle mark areas; use aboveRoads / aboveLabels to control stacking with roads and labels—aboveRoads puts map labels above the overlay, aboveLabels puts the overlay above labels

Map controls

Map {
    // Content
}
.mapControls {
    MapUserLocationButton()
    MapCompass()
    MapScaleView()
}

Key points:

  • mapControls automatically lays out controls in appropriate positions
  • For manual placement use mapScope association: .mapScope(mapScope)
  • macOS additionally provides MapZoomStepper and MapPitchSlider

Core Takeaways

  • Add an interactive map to local lifestyle apps: Restaurant, hotel, and event apps can quickly implement “nearby recommendations” with MapKit for SwiftUI. Markers automatically show category icons, Look Around provides street-level previews—users can judge a place without leaving the app.

  • Use maps for data visualization: If your app has location-related data (delivery routes, workout paths, travel footprints), display it directly on the map with MapPolyline and custom Annotations—more intuitive than a list. Combined with onMapCameraChange, you can load only data points in the current viewport.

  • Build a trip planning tool: Follow the Boston itinerary approach from the session—search for attractions, mark with Markers, connect with routes, preview with Look Around. SwiftUI’s declarative syntax dramatically reduces the implementation cost of this multi-step interaction.

Comments

GitHub Issues · utterances