Highlight
MapKit 今年的更新围绕”地点”这个核心概念展开,三个关键词:引用、展示、搜索。
核心内容
过去在 App 里处理地点数据,开发者只能存经纬度。问题是坐标会过时——Apple Maps 的数据编辑随时可能修正一个地点的位置,你的 App 里的旧坐标就错了。营业时间、电话号码这些信息也在持续变动,硬编码根本跟不住。
今年 MapKit 引入了 Place ID 体系来解决这个问题。Apple Maps 里的每个地点(餐厅、博物馆、公园、学校等)都有一个唯一且持久的标识符。你用 Place ID 做主键存到自己的数据库,下次通过 ID 查询时拿到的就是 Apple 最新维护的数据,包括更新后的位置、网址和营业时间。标识符在原生端和 Web 端通用,可以持久化、可以跨平台共享(01:01)。
展示层面,MapKit 把 Apple Maps 的 Place Card 能力开放给了第三方。通过 SelectionAccessory API,用户点击地图标注后弹出和系统地图一样风格的地点详情卡片,支持全屏、紧凑和跳转 Maps 三种展示模式(06:30)。即使你的页面没有地图视图,MapItemDetail 和 PlaceDetail 也可以独立渲染地点信息,UIKit / AppKit / SwiftUI 均有对应 API。Web 端则新增了 Maps Embed API,不用写 JavaScript 就能嵌入一个带 Place Card 的地图。
搜索 API 同步增强:新增 AddressFilter 可以限定只搜索城市或邮编等地址类型,RegionPriority 可以强制结果落在指定区域内,服务器 API 还加入了分页支持(12:12)。
详细内容
Place ID:用标识符代替坐标
拿到 Place ID 有两种方式:用已有的 Search / Geocoding API 获取,或者用 Apple 新提供的 Place ID Lookup 工具批量查找。Lookup 工具在 developer.apple.com/maps 的 Resources 页面,支持搜索和地图点击两种交互(02:30)。
拿到 ID 后,在 Swift 端通过 MKMapItemRequest 异步获取 MKMapItem(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
}
}
}
MKMapItem.Identifier(rawValue:)把 Place ID 字符串转为类型安全的标识符MKMapItemRequest发起异步请求,从 Apple 服务器拉取最新地点数据- 用
Marker(item:)直接渲染,无需手动设置坐标——位置由 Apple 数据驱动 - 如果 Apple Maps 编辑修正了地点位置,使用 Place ID 的 App 自动继承变更,不需要改代码
Web 端对应的是 mapkit.PlaceLookup(03:44),用同一个 Place ID 查到 Place 对象后,通过 PlaceAnnotation 渲染标注。Token 这一年也简化了:新 token provisioning 工具生成的是域绑定的静态字符串,不再需要动态 JWT 生成,也不会过期直到你手动撤销(04:46)。
SelectionAccessory:在地图上弹出 Place Card
给地图上的标注加 Place Card,SwiftUI 只需一个修饰符(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()
}
}
}
mapItemDetailSelectionAccessory()默认使用 automatic 模式,MapKit 根据平台和地图尺寸自动选择全屏或紧凑展示- 传入
.callout参数可以强制使用气泡式弹出
更实用的场景:让地图上的所有 POI 都支持 Place Card,不只是你自己的标注(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)
}
}
MapSelection<MKMapItem>是统一的选中类型,同时支持自定义标注和地图上的 POI.mapFeatureSelectionAccessory(.callout)让用户点击地图上任意 POI 时也能弹出 Place Card- 用户探索你标注周边的公园、餐厅等地点时,直接获得上下文信息
没有地图视图的场景,可以用 mapItemDetailSheet(item:) 在 List 中展示 Place Card(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)
}
}
mapItemDetailSheet在非地图上下文中弹出 Place Card,支持 UIKit / AppKit / SwiftUI- Web 端等价 API 是
mapkit.PlaceDetail,配合mapkit.PlaceSelectionAccessory使用
搜索增强:AddressFilter 与 RegionPriority
搜索”库比蒂诺”时不想匹配到同名商家?用 AddressFilter 限定只搜索城市(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 ?? []
}
}
MKAddressFilter(including: .locality)只返回城市级别结果,过滤掉同名商家request.regionPriority = .required强制搜索结果必须落在指定区域内,缩放地图后不会出现远处的标注- 搜索还支持更多地点类型:音乐场馆、滑板公园、城堡,以及河流和山脉等自然地貌(12:18)
- Web 端对应
mapkit.AddressFilter.including([mapkit.AddressCategory.Locality])和mapkit.Search.RegionPriority.Required
核心启发
-
做什么:用 Place ID 替换数据库中的经纬度字段。为什么值得做:坐标是会过时的数据——Apple Maps 编辑修正位置后,硬编码的坐标就错了;Place ID 是稳定引用,查询时总是拿到 Apple 最新维护的信息。怎么开始:对新添加的地点直接存 Place ID;存量数据通过 Place ID Lookup 工具逐批迁移。
-
做什么:给地图加上
.mapFeatureSelectionAccessory(.callout)。为什么值得做:用户点击地图上任何 POI 都能看到 Place Card,获得周边上下文(公园、餐厅等),这对探索型 App 价值很高,而且只需要一行代码。怎么开始:在现有Map视图上加这个修饰符,不需要改动已有的标注逻辑。 -
做什么:搜索时使用 AddressFilter + RegionPriority 组合。为什么值得做:避免搜索”Cupertino”返回同名商家的干扰结果;避免缩放地图后远处标注污染视野。怎么开始:城市搜索加
addressFilter: .locality,区域搜索加regionPriority: .required,两个属性各加一行即可。 -
做什么:用 Maps Embed API 在网页上展示地点信息。为什么值得做:不需要写 JavaScript,在 Apple 开发者网站的 Create a Map 工具里配置 Place ID 和 token,复制生成的 HTML 片段即可,适合只展示单个地点的场景。怎么开始:到 developer.apple.com/maps → Resources → Create a Map,选择 Web Embed 模式。
关联 Session
- What’s new in location authorization — 位置授权机制全面升级,与 MapKit 定位能力配合使用
- Bring context to today’s weather — WeatherKit 新增历史对比和时段摘要,与地图场景天然搭配
- What’s new in AppKit — macOS 端也支持 MapKit Place Card 渲染
- Optimize for the spatial web — Safari 与 Web 技术更新,MapKit JS 同属 Web 端生态
评论
GitHub Issues · utterances