With iOS 26, Apple deprecated CLGeocoder — the API developers have relied on since Core Location’s earliest days to turn a coordinate into a readable place: city, region, nearby point of interest. The replacement lives in MapKit: MKReverseGeocodingRequest.1
Let’s take one coordinate as an example, about 20 metres (60 feet) away from Cypress Mountain Resort’s main cabin, located in the city of West Vancouver, BC in Canada:
Below is the code I ran both reverse-geocoding processes, side by side:
let location = CLLocation(latitude: 49.396840, longitude: -123.204116)
func geocoderLookup() async -> String {
let coder = CLGeocoder()
if let placemarks = try? await coder.reverseGeocodeLocation(location)
{
let output = placemarks.map({ mark in
var output = ""
output += (mark.areasOfInterest ?? []).joined(separator: " | ") + "\n"
output += (mark.subLocality ?? "") + "\n"
output += (mark.locality ?? "") + "\n"
output += (mark.subAdministrativeArea ?? "") + "\n"
output += (mark.administrativeArea ?? "") + "\n"
output += (mark.country ?? "") + "\n"
return output
}).joined(separator: "\n\n")
return output
} else {
return "Error"
}
}
func mapkitLookup() async -> String {
let request = MKReverseGeocodingRequest(location: location)
let items = (try? await request?.mapItems) ?? []
let output = items.map({ item in
var output = ""
output += "\(item.pointOfInterestCategory?.rawValue ?? "NA"): \(item.name ?? "No Name")" + "\n"
output += (item.address?.fullAddress ?? "") + "\n"
output += (item.address?.shortAddress ?? "") + "\n"
output += (item.addressRepresentations?.cityName ?? "") + "\n"
output += (item.addressRepresentations?.regionName ?? "") + "\n"
output += (item.addressRepresentations?.cityWithContext(.full) ?? "") + "\n"
output += (item.addressRepresentations?.cityWithContext(.short) ?? "") + "\n"
output += (item.addressRepresentations?.cityWithContext(.automatic) ?? "") + "\n"
output += (item.addressRepresentations?.fullAddress(includingRegion: true, singleLine: true) ?? "") + "\n"
return output
}).joined(separator: "\n\n")
return output
}
Here are the results, side by side:
| CLGeocoder | MapKit | |
|---|---|---|
| Point of Interest | a) Cypress Mountain b) Cypress Provincial Park |
Cypress Mountain |
| Neighbourhood | Cypress Park | 🫥 N/A |
| City | ✅ West Vancouver | ❌ Bowen Island |
| Area | ✅ Metro Vancouver | 🫥 N/A |
| Province | ✅ BC | ⚠️ (city), BC |
| Country | ✅ Canada | ✅ Canada |
| Postal Code | ❌ V0N 1G2 | ❌ V0N 1G2 |
CLGeocoder got it right on every level: Cypress Mountain (also matched as Cypress Provincial Park), in West Vancouver, in Metro Vancouver, in BC, Canada.
MapKit named the point of interest correctly, too — it knew this was Cypress Mountain. But every level above that was wrong. It placed the address, the city, and the region under Bowen Island — a separate island across Howe Sound, with no relationship to the mountain behind West Vancouver whatsoever.
Just for the reference, here are Bowen Island and West Vancouver on the same map:
That’s not a formatting difference or a naming preference between two reasonable answers. It’s just wrong.
It’s not just about correctness
Beyond correctness, MapKit’s AddressRepresentations also don’t get me clean information that I can work with:
-
I cannot get separate names of the city or of the province (Americans: read state). All I could get is a city name on its own (Portland) or the readable city name with disambiguation (Portland, OR).
-
I simply cannot get neighbourhood and metropolitan information at all. Those are available as
subLocalityandsubAdministrativeAreafrom aCLPlacemarkobject. While as a Canadian, I don’t care about metropolitan as much since it has no impact on elections, sub-administrative area is more meaningful to Americans because this is where you would find your county names — the level between a city and a state. -
I cannot find waterbodies — available in CLPlacemark in
.oceanand.inlandWater, respectively. -
CLPlacemark contains as many qualifying points of interests as it seems able. MapKit, however, doesn’t even return the provincial park as a POI.
So the MapKit replacement is not only inaccurate, it contains less information and is harder to work with.
A replacement that (still) isn’t ready
I don’t doubt Apple will get MapKit’s reverse geocoding sorted out eventually — it clearly has all the right data, since it named the point of interest correctly. But “eventually” isn’t “now,” and CLGeocoder having a deprecation warning on it doesn’t change that its output is the one you can trust today. Until MapKit stops making mistakes like this, I’m not switching, warning or not.
-
CLGeocoder’s documentation has a coy deprecation message of two words: “Use MapKit”. There’s no link to the docs of MapKit, and there is no example. The deprecation message is not even a complete sentence with a period. My cynical read is that whoever wrote this two-word deprecation message was too embarrassed to even write up a full sentence, or to furnish the deprecation with an example. ↩