WWDC Quick Look đź’“ By SwiftGGTeam
Unlock the power of places with MapKit

Unlock the power of places with MapKit

Watch original video

Highlight

MapKit’s updates this year center on the core concept of “places,” with three keywords: reference, display, and search.


Core Content

In the past, when handling place data in apps, developers could only store latitude and longitude. The problem is that coordinates go stale—Apple Maps editors can correct a place’s location at any time, and the old coordinates in your app become wrong. Business hours, phone numbers, and other information also change continuously; hardcoding simply cannot keep up.

This year, MapKit introduces a Place ID system to solve this. Every place in Apple Maps (restaurants, museums, parks, schools, and more) has a unique, persistent identifier. You store Place IDs as primary keys in your database, and when you query by ID later, you get the latest data maintained by Apple, including updated location, website, and business hours. Identifiers work across native and web, can be persisted, and can be shared across platforms (01:01).

On the display side, MapKit opens Apple Maps’ Place Card capability to third parties. With the SelectionAccessory API, when users tap a map annotation, a place detail card in the same style as the system Maps app appears, supporting full-screen, compact, and open-in-Maps presentation modes (06:30). Even if your page has no map view, MapItemDetail and PlaceDetail can render place information independently, with corresponding APIs in UIKit, AppKit, and SwiftUI. On the web, the new Maps Embed API lets you embed a map with a Place Card without writing JavaScript.

Search APIs are enhanced in parallel: the new AddressFilter can limit searches to address types such as cities or postal codes, RegionPriority can force results to fall within a specified region, and the server API adds pagination support (12:12).

Detailed Content

Place ID: Identifiers Instead of Coordinates

There are two ways to get a Place ID: use the existing Search / Geocoding API, or use Apple’s new Place ID Lookup tool for batch lookup. The Lookup tool is on the Resources page at developer.apple.com/maps, supporting both search and map-click interactions (02:30).

After you have the ID, fetch MKMapItem asynchronously on the Swift side via MKMapItemRequest (03:06):

struct PlaceMapView: View {
    var placeID: String // "I63802885C8189B2B"

    @State private var item: MKMapItem?

    var body: some View {
        Map {
            if let item {
                Marker(item: item)
            }
        }
        .task {
            guard let identifier = MKMapItem.Identifier(
                rawValue: placeID
            ) else {
                return
            }
            let request = MKMapItemRequest(
                mapItemIdentifier: identifier
            )
            item = try? await request.mapItem
        }
    }
}

Key points:

  • MKMapItem.Identifier(rawValue:) converts a Place ID string into a type-safe identifier
  • MKMapItemRequest initiates an async request to fetch the latest place data from Apple servers
  • Use Marker(item:) to render directly without manually setting coordinates—position is driven by Apple data
  • If Apple Maps editors correct a place’s location, apps using Place ID inherit the change automatically without code changes

On the web, the counterpart is mapkit.PlaceLookup (03:44). After looking up a Place object with the same Place ID, render the annotation via PlaceAnnotation. Tokens were also simplified this year: the new token provisioning tool generates domain-bound static strings, no longer requiring dynamic JWT generation, and they do not expire until you manually revoke them (04:46).

SelectionAccessory: Place Cards on the Map

To add a Place Card to map annotations, SwiftUI needs only one modifier (07:32):

struct VisitedStoresView: View {
    var visitedStores: [MKMapItem]
    @State private var selection: MKMapItem?

    var body: some View {
        Map(selection: $selection) {
            ForEach(visitedStores, id: \.self) { store in
                Marker(item: store)
            }
            .mapItemDetailSelectionAccessory()
        }
    }
}

Key points:

  • mapItemDetailSelectionAccessory() uses automatic mode by default; MapKit chooses full-screen or compact presentation based on platform and map size
  • Pass .callout to force callout-style presentation

A more practical scenario: enable Place Cards for all POIs on the map, not just your own annotations (11:17):

struct VisitedStoresView: View {
    var visitedStores: [MKMapItem]
    @State private var selection: MapSelection<MKMapItem>?

    var body: some View {
        Map(selection: $selection) {
            ForEach(visitedStores, id: \.self) { store in
                Marker(item: store)
                    .tag(MapSelection(store))
            }
            .mapItemDetailSelectionAccessory(.callout)
        }
        .mapFeatureSelectionAccessory(.callout)
    }
}

Key points:

  • MapSelection<MKMapItem> is a unified selection type supporting both custom annotations and map POIs
  • .mapFeatureSelectionAccessory(.callout) lets users tap any POI on the map to show a Place Card
  • When users explore parks, restaurants, and other places near your annotations, they get contextual information directly

For scenarios without a map view, use mapItemDetailSheet(item:) to show a Place Card in a List (09:15):

struct StoreList: View {
    var stores: [MKMapItem]
    @State private var selectedStore: MKMapItem?

    var body: some View {
        List(
            stores,
            id: \.self,
            selection: $selectedStore
        ) {
            Text($0.name ?? "Apple Store")
        }
        .mapItemDetailSheet(item: $selectedStore)
    }
}

Key points:

  • mapItemDetailSheet presents a Place Card outside a map context, supporting UIKit, AppKit, and SwiftUI
  • The equivalent web API is mapkit.PlaceDetail, used with mapkit.PlaceSelectionAccessory

Search Enhancements: AddressFilter and RegionPriority

Searching for “Cupertino” but don’t want to match a business with the same name? Use AddressFilter to limit the search to cities (14:41):

struct CoffeeMap: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var coffeeShops: [MKMapItem] = []

    var body: some View {
        Map(position: $position) {
            ForEach(coffeeShops, id: \.self) { café in
                Marker(item: cafe)
            }
        }
        .task {
            guard let cupertino = await findCity() else {
                return
            }
            coffeeShops = await findCoffee(in: cupertino)
        }
    }

    private func findCity() async -> MKMapItem? {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = "cupertino"

        request.addressFilter = MKAddressFilter(
            including: .locality
        )

        let search = MKLocalSearch(request: request)
        let response = try? await search.start()
        return response?.mapItems.first
    }

    private func findCoffee(in city: MKMapItem ) async -> [MKMapItem] {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = "coffee"
        let downtown = MKCoordinateRegion(
            center: city.placemark.coordinate,
            span: .init(
                latitudeDelta: 0.01,
                longitudeDelta: 0.01
            )
        )
        request.region = downtown
        request.regionPriority = .required
        let search = MKLocalSearch(request: request)
        let response = try? await search.start()
        return response?.mapItems ?? []
    }
}

Key points:

  • MKAddressFilter(including: .locality) returns only city-level results, filtering out businesses with the same name
  • request.regionPriority = .required forces search results to fall within the specified region; distant annotations won’t appear after zooming the map
  • Search also supports more place types: music venues, skate parks, castles, and natural features such as rivers and mountains (12:18)
  • On the web, use mapkit.AddressFilter.including([mapkit.AddressCategory.Locality]) and mapkit.Search.RegionPriority.Required

Core Takeaways

  • What to build: Replace latitude/longitude fields in your database with Place IDs. Why it’s worth doing: Coordinates are stale data—after Apple Maps editors correct a location, hardcoded coordinates are wrong; Place ID is a stable reference that always returns the latest information maintained by Apple. How to start: Store Place IDs directly for newly added places; migrate existing data in batches via the Place ID Lookup tool.

  • What to build: Add .mapFeatureSelectionAccessory(.callout) to your map. Why it’s worth doing: Users can see a Place Card when tapping any POI on the map, gaining surrounding context (parks, restaurants, etc.), which is highly valuable for exploration apps—and it takes only one line of code. How to start: Add this modifier to your existing Map view without changing your annotation logic.

  • What to build: Use AddressFilter + RegionPriority together in search. Why it’s worth doing: Avoids noisy results when searching for “Cupertino” that match businesses with the same name; prevents distant annotations from cluttering the view after zooming. How to start: Add addressFilter: .locality for city searches and regionPriority: .required for regional searches—one line each.

  • What to build: Use the Maps Embed API to display place information on web pages. Why it’s worth doing: No JavaScript required—configure Place ID and token in the Create a Map tool on the Apple Developer site, copy the generated HTML snippet, ideal for single-place display scenarios. How to start: Go to developer.apple.com/maps → Resources → Create a Map, and choose Web Embed mode.

Comments

GitHub Issues · utterances