WWDC Quick Look đź’“ By SwiftGGTeam
What's new in MapKit

What's new in MapKit

Watch original video

Highlight

In iOS 26, MapKit ships PlaceDescriptor, a unified MKGeocodingRequest/MKReverseGeocodingRequest pair, a cycling transportType, and Look Around views in MapKit JS.


Core content

Many apps share the same map problem: you only have an address or a latitude/longitude pair, but you can’t get the rich place data that Apple Maps shows. The old options were either to draw a thin annotation yourself, or to call MKLocalSearch and sift through the results. Both feel awkward for CRM sync, passing values between App Intents, or referencing one specific place across processes.

This year MapKit adds PlaceDescriptor, shipped in the new GeoToolbox framework. It packs a commonName, address, coordinate, and device location into one structured reference, then hands it to MKMapItemRequest to resolve into a full MKMapItem. The resolved MapItem matches what you would get from a Place ID: the official name, icon, hours, and a Place Card all work out of the box.

Geocoding gets a single entry point too. MKGeocodingRequest (address to coordinate) and MKReverseGeocodingRequest (coordinate to address) replace CLGeocoder for MapKit work. The directions API adds transportType = .cycling, making cycling routes a first-class citizen for the first time. MapKit JS catches up with an interactive Look Around view and event callbacks, so the web can embed the 3D street view directly.


Detail

The minimal PlaceDescriptor needs one representation and a commonName:

// Creating and resolving a PlaceDescriptor with coordinate PlaceRepresentation

import GeoToolbox
import MapKit

let annaLiviaCoordinates = CLLocationCoordinate2D(
    latitude: 53.347673,
    longitude: -6.290198
)
let annaLiviaDescriptor =  PlaceDescriptor(
    representations: [.coordinate(annaLiviaCoordinates)],
    commonName: "Anna Livia Fountain"
)

let request = MKMapItemRequest(placeDescriptor: annaLiviaDescriptor)
do {
    annaLiviaMapItem = try await request.mapItem
} catch {
    print("Error resolving placeDescriptor: \(error)")
}

Key points:

  • import GeoToolbox: PlaceDescriptor lives outside the main MapKit module, so import the new framework on its own.
  • representations: [.coordinate(...)]: the representations array is ordered by precision, highest first. Here there is only a coordinate.
  • commonName: must be a publicly known name (such as Sydney Opera House). Don’t pass private aliases like “Mom’s house” — they don’t help resolution.
  • MKMapItemRequest(placeDescriptor:): asks the Apple Maps backend to resolve the descriptor into an MKMapItem.
  • try await request.mapItem: throws on failure, so wrap it in do/catch.

If the source data is an address rather than a coordinate, swap the representation for .address (05:56):

// Creating and resolving a PlaceDescriptor with address PlaceRepresentation

import GeoToolbox
import MapKit

let address = "121-122 James's St, Dublin 8"
let descriptor =  PlaceDescriptor(
    representations: [.address(address)],
    commonName: "Obelisk Fountain"
)

let request = MKMapItemRequest(placeDescriptor: descriptor)
do {
    obeliskFountain = try await request.mapItem
} catch {
    print("Error resolving placeDescriptor: \(error)")
}

Key points:

  • .address(address): pass the most complete address you have — street number, city, postal code — for a higher hit rate.
  • The order of the representations array matters. If the coordinate was reverse-derived from the address, list the address first.
  • The resolution logic lives on the MapKit backend; the caller only supplies the description.

When passing a place across apps (for example, App Intents), use supportingRepresentations to attach IDs from several services (06:45):

// Creating a PlaceDescriptor with identifiers

import GeoToolbox

let annaLiviaCoordinates = CLLocationCoordinate2D(
    latitude: 53.347673,
    longitude: -6.290198
)
let identifiers = ["com.apple.MapKit" : "ICBB5FD7684CE949"]
let annaLiviaDescriptor =  PlaceDescriptor(
    representations: [.coordinate(annaLiviaCoordinates)],
    commonName: "Anna Livia Fountain",
    supportingRepresentations: [.serviceIdentifiers(identifiers)]
)

Key points:

  • serviceIdentifiers is a dictionary: the key is the map service’s bundle id (com.apple.MapKit), the value is that service’s place id.
  • If a MapKit identifier is present, MKMapItemRequest uses it first, falling back to the coordinate or address representation only when that fails.
  • You can list IDs from several services side by side, so the receiver can pick the one its SDK understands. That is what makes PlaceDescriptor a good fit for App Intents parameters.

A cycling route is one extra line on the existing MKDirections.Request (16:06):

// Fetch a cycling route

let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = selectedItem
request.transportType = .cycling
let directions = MKDirections(request: request)
do {
    let response = try await directions.calculate()
    returnedRoutes = response.routes
} catch {
    print("Error calculating directions: \(error)")
}

Key points:

  • transportType = .cycling: this year’s new case. Before, only walking, automobile, and transit were supported.
  • MKMapItem.forCurrentLocation(): when the source is the current location, you don’t have to build a coordinate.
  • directions.calculate() is still the async throwing API.
  • The returned response.routes can be drawn straight on a SwiftUI Map with MapPolyline.

Reverse geocoding uses MKReverseGeocodingRequest instead of CLGeocoder (10:45):

// Reverse geocode with MapKit

import MapKit

let millCreekCoordinates = CLLocation(latitude: 39.042617, longitude: -94.587526)
if let request = MKReverseGeocodingRequest(location: millCreekCoordinates) {
    do {
        let mapItems = try await request.mapItems
        millCreekMapItem = mapItems.first
    } catch {
        print("Error reverse geocoding location: \(error)")
    }
}

Key points:

  • The initializer returns an optional. If the if let fails, the input coordinate is invalid.
  • request.mapItems returns an MKMapItem array directly, skipping the CLPlacemark to MKMapItem conversion.
  • The first element is usually the best match, but check confidence against your business rules.

Takeaways

  1. Feed your existing CRM or Excel address data into PlaceDescriptor. Many app backends only carry names and addresses, so they couldn’t render Apple Maps style annotations on the map. Why it pays off: once shipped, users see the official hours, phone number, and Place Card inside your app. How to start: replace CLGeocoder.geocodeAddressString with PlaceDescriptor(representations: [.address(...)], commonName:) plus MKMapItemRequest. One change covers the whole import pipeline.

  2. Wire transportType = .cycling into cycling apps. Until now you either reused walking routes or bolted on a third-party cycling API. Why it pays off: Apple Maps cycling data covers elevation, gradient, and bike-lane preferences, so the native experience is complete. How to start: add one line, request.transportType = .cycling, to your existing MKDirections.Request code. Verify route quality before exposing a mode switch to users.

  3. Use PlaceDescriptor as an App Intents parameter. Passing a CLLocation across apps loses the place name and the IDs. Why it pays off: Shortcuts and Siri can pass arguments like “the X store nearby” to your app reliably. How to start: switch the location field on existing IntentParameters to PlaceDescriptor, and attach your service’s ID through supportingRepresentations.

  4. Embed Look Around views in MapKit JS. The web has long been stuck with 2D maps, sending users to third parties for street view. Why it pays off: travel and real-estate sites can show Apple’s 3D street view right on the detail page, which lifts time on page. How to start: fetch a Place with mapkit.PlaceLookup, then build mapkit.LookAround(container, place, options). Use LookAroundPreview as a thumbnail placeholder and expand to the full view on click.

  5. Rewrite old CLGeocoder calls as MKGeocodingRequest or MKReverseGeocodingRequest. The old API returns CLPlacemark, which still has to be converted to MKMapItem. Why it pays off: one fewer layer, one fewer place to fail. How to start: grep the codebase for CLGeocoder(), then swap address calls for MKGeocodingRequest and coordinate calls for MKReverseGeocodingRequest. Mind the optional initializer.


Comments

GitHub Issues · utterances