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 identifierMKMapItemRequestinitiates 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
.calloutto 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:
mapItemDetailSheetpresents a Place Card outside a map context, supporting UIKit, AppKit, and SwiftUI- The equivalent web API is
mapkit.PlaceDetail, used withmapkit.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 namerequest.regionPriority = .requiredforces 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])andmapkit.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 existingMapview 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: .localityfor city searches andregionPriority: .requiredfor 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.
Related Sessions
- What’s new in location authorization — Location authorization gets a major upgrade; pairs with MapKit location capabilities
- Bring context to today’s weather — WeatherKit adds historical comparisons and period summaries, a natural fit for map scenarios
- What’s new in AppKit — macOS also supports MapKit Place Card rendering
- Optimize for the spatial web — Safari and web technology updates; MapKit JS is part of the web ecosystem
Comments
GitHub Issues · utterances