Go further with MapKit
Machine Learning & AI 进阶 45m

深入探索 MapKit

Go further with MapKit

2025年6月10日

在 Apple 官方观看视频

一句话判断

如果你的 app 里有任何”把别人的地点 ID 塞进 MapKit”的胶水代码,这场 Session 直接帮你删掉它。

这场 Session 讲了什么

MapKit 一直有个尴尬的问题:你的地点数据来自高德、Google Places、自家后端,但 MapKit 只认自己的 Place ID。开发者被迫写一堆转换逻辑,把坐标、地址、第三方 ID 来回倒腾。这次 Apple 推出的 PlaceDescriptor 就是来干掉这层胶水的——它用一个 representations 数组把坐标、地址、服务标识符打包在一起,MapKit 自己挑最合适的去用。你不再需要先查一个 MapKit Place ID 才能操作地点。

另一个大动作是地理编码(Geocoding)从 CoreLocation 搬家到 MapKit。CLGeocoderCLPlacemark 被废弃了,取而代之的是 MKReverseGeocodingRequest,返回的不再是那个臃肿的 CLPlacemark(里面有时区、海洋标识这些你永远用不到的字段),而是干净的 MKMapItem 加上全新的 MKAddress 类型。地址格式化终于不用自己拼字符串了。

骑行路线扩展到了 watchOS,Look Around 街景登陆 MapKit JS。骑行时看手表比掏手机自然得多,这个设备选择很合理。Look Around 在 Web 端的落地则直接抢了 Google Street View 的地盘,房产和旅游类网站终于有了 Apple 生态的街景方案。这几个变化单独看不大,但放在一起说明 Apple 在把 MapKit 从”iOS 地图控件”推向”全平台地点基础设施”——一个你可以在 iPhone、Apple Watch、Web 页面上统一调用的地点智能层。

值得深挖的点

PlaceDescriptor 的设计取舍

PlaceDescriptor 最有意思的地方是它的 representations 数组按优先级排列。最精确的信息排前面,MapKit 按顺序尝试匹配。这意味着你可以同时塞进坐标、模糊地址、高德的 POI ID,系统自己决定用哪个。这个设计的好处是容错性高——坐标偏了还有地址兜底,地址不精确还有服务标识符。但代价是你得把所有能拿到的信息都塞进去,数据量会膨胀。

serviceIdentifiers 字段用 Bundle identifier -> Place ID 的映射来实现跨服务引用。理论上你可以在 PlaceDescriptor 里声明”这个地点在高德是 xxx,在 Google 是 yyy”,MapKit 理解这个映射关系后就能在不同服务间打通地点引用。这比你手动维护一张”高德 ID -> Apple ID”的对照表优雅太多了。

但这里有个现实问题:Apple 并没有公开哪些第三方服务被支持,这个映射的覆盖范围是个黑盒。文档里给了 API,但没给你一张”支持的服务列表”。在中国市场,高德和百度的 POI 精度通常优于 Apple Maps,serviceIdentifiers 能不能真正打通这些数据源,得实测才知道。另一个容易忽略的问题是 Bundle identifier 的归属——serviceIdentifiers 的 key 应该是调用方应用自己的 bundle identifier,不是目标服务的标识符。这个语义反直觉,开发时很容易搞反。如果你的 app 重度依赖国内地图数据,别急着把现有的坐标转换逻辑删掉,先在真机上验证 PlaceDescriptor 在你的具体场景下能不能返回准确结果。

CLGeocoder 退役的影响

CLGeocoder 从 iOS 5 用到现在,废弃它是个大胆的决定。新的 MKReverseGeocodingRequest 返回 MKMapItem,信息确实更丰富——名称、电话、URL、分类一应俱全。新增的 MKAddress 类型提供 fullAddressshortAddress 等预设格式,还有 MKAddressRepresentations 支持按语言环境自定义展示。这对做多语言 app 的团队是实打实的简化:以前你得自己判断 locale 然后拼地址字符串,现在框架帮你搞定。

但迁移有个坑:MKReverseGeocodingRequest 返回的是数组,不是单个结果。精度要求不高的场景取 first 没问题,但导航目的地确认这种场景你得遍历匹配。另外 address.representations 是异步属性,获取时需要 await——如果你的代码是同步写的,这不只是换 API,是调用链的架构调整。现有的同步地理编码逻辑需要拆成异步流程,涉及的上下游代码都得跟着改。这个工作量在大项目里可能比想象的大,建议先评估一下你有多少处调用了 CLGeocoder

代码片段

用 PlaceDescriptor 桥接外部地点数据

场景:你的 app 从后端拿到一批高德坐标,要在 MapKit 里显示和搜索。

import MapKit
import GeoToolbox

let place = PlaceDescriptor(
    coordinate: CLLocationCoordinate2D(latitude: 39.9042, longitude: 116.4074),
    address: "北京市东城区天安门广场",
    serviceIdentifiers: [Bundle.main.bundleIdentifier!: "gaode_poi_001"]
)

let request = MKMapItemRequest(placeDescriptor: place)
request.getMapItem { mapItem, error in
    guard let item = mapItem else { return }
    print(item.name ?? "未知")
}

坑:representations 为空时查询直接失败,务必确保至少提供一种表达。

新版反向地理编码

场景:外卖配送场景,把骑手 GPS 坐标转成用户可读地址。

import MapKit

func reverseGeocode(coordinate: CLLocationCoordinate2D) async throws -> String? {
    let request = MKReverseGeocodingRequest(coordinate: coordinate)
    let mapItems = try await request?.mapItems
    guard let address = mapItems?.first?.address else { return nil }
    return address.fullAddress
}

坑:MKReverseGeocodingRequest 在网络不可用时可能返回空结果,生产环境建议加离线缓存。

骑行路线规划

场景:共享单车 app 计算骑行路线和预计时间。

import MapKit

func planCyclingRoute(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) async throws -> MKRoute? {
    let request = MKDirections.Request()
    request.source = MKMapItem(placemark: MKPlacemark(coordinate: from))
    request.destination = MKMapItem(placemark: MKPlacemark(coordinate: to))
    request.transportType = .cycling

    let response = try await MKDirections(request: request).calculate()
    return response.routes.first
}

坑:.cycling 在部分国家和地区不可用,返回空 routes 不代表代码有问题,可能只是该地区不支持。

最佳实践

建议分三步走。

第一步,先别急着迁移。CLGeocoder 虽然被废弃了,但短期内不会被移除——Apple 废弃一个从 iOS 5 就存在的 API,通常会给至少两个大版本的缓冲期。花一两天时间在核心场景里跑一下 MKReverseGeocodingRequest,对比新旧 API 返回的地址数据质量,尤其是中文地址的格式化效果。MKAddressfullAddress 对中文地址的处理逻辑跟 CLPlacemark 不一定一致,比如它可能会省略”中国”前缀或者调整省市区的顺序。如果目标市场主要在国内,这个细节值得认真对比。

第二步,PlaceDescriptor 先在新功能里用。如果正在做”收藏地点”或”分享位置”这类新功能,直接用 PlaceDescriptor 来建模地点数据——这是它最舒服的使用姿势。但对于已有的、跑得很稳的地点查询逻辑,没动力就别动。PlaceDescriptor 主要解决的是”没有 MapKit Place ID”的场景,不是让开发者把现有的 Place ID 逻辑重写一遍。如果现在的代码里有一张”第三方 ID -> MapKit Place ID”的映射表,保持现状就好,PlaceDescriptor 不会让这张表变得多余。

第三步,骑行路线可以马上用。这个改动风险最低,API 跟驾车路线几乎一致,只是 transportType 换一下。如果 app 有骑行相关场景,这是一个投入产出比很高的小改进。

还有什么值得关注

  • GeoToolbox 是个新框架,目前公开的能力主要服务于 PlaceDescriptor 和地理编码,但它的定位是”跨平台地理工具箱”,后续大概率会扩展更多地理数据处理能力。
  • Look Around 街景登陆 MapKit JS,意味着房产、旅游类 Web 应用终于能嵌入 Apple 街景了,之前这块是 Google 的地盘。
  • MKAddressRepresentations 支持按语言环境自定义地址格式,做国际化的团队可以用它干掉一大段手动拼接地址的工具代码。
MapKit GeoToolbox PlaceDescriptor Geocoding