WWDC Quick Look 💓 By SwiftGGTeam
Unlock the power of places with MapKit

Unlock the power of places with MapKit

观看原视频

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 异步获取 MKMapItem03: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.PlaceLookup03: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

评论

GitHub Issues · utterances