大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 MapKit 充分优化位置相关功能
探索你可以通过哪些强大的新方式使用 MapKit 和 MapKit JS 将地图整合到 App 和网站中。了解如何使用 Place ID 存储和引用唯一的位置。查看搜索方面的改进,让顾客能够更高效地查找相关位置。了解让你能为位置显示丰富信息的全新 Place Card API,以便顾客能够直接在你的 App 中探索目的地。此外,我们将介绍如何借助简化的令牌预置流程和 Web Embed API,快速将地图嵌入到你的网站中。
章节
- 0:00 - Introduction
- 0:54 - Reference a place
- 6:12 - Display place details
- 12:05 - Find a place
资源
- Displaying place information using the Maps Embed API
- Forum: Maps & Location
- Identifying unique locations with Place IDs
- Interacting with nearby points of interest
- Resources - Apple Maps - Apple Developer
相关视频
WWDC23
-
下载
欢迎大家 我叫 Mike 大家好 我叫 Jeff 我们是 MapKit 工程师 今天很高兴与大家探讨 MapKit 框架和 MapKit JS 中 适用于网页的一些新功能 很多时候 地图的主要作用其实 就是标识可在地图上找到的地点 当你使用“Apple 地图”App 浏览或探索地点时 你可以获得全球各地 不同地点的大量信息 今年 MapKit 新增了一些功能 让你能以全新的方式将这些地点 引入你的 App 要介绍的内容很多! 我们将重点讨论三个主要用例 具体来说 我们将讨论 如何使用标识符引用地点 如何在 App 的 UI 中 显示地点详情 以及如何使用 Search API 增强功能 有效查找地点 首先 我将介绍如何引用地点 今年 我们引入了地点标识符 地点 ID 可用于引用 MapKit 框架中由地图项表示的地点 和 MapKit JS 中 由地点对象表示的地点 使用地点 ID 可以引用各种地点 你可以将它用于博物馆、餐厅、 公园或学校等兴趣点 App 可以使用它来引用 作家在即将举行的 新书巡回签售会上要到访的书店 引用是唯一的 而且能够 随时间推移保持有效状态; 只要地点存在 它们便一同存在 图书巡回签售会 App 可能希望 为巡回签售会中到访的每家书店 提供一个网站链接 只要 Apple 更新了书店的数据 例如网站 URL 使用地点 ID 的 App 就能显示这些新信息 由于这些标识符是唯一的 因此 App 可以用它们 作为自身数据结构中的键 标识符可以持久保存和共享 Apple 会追踪数据 你可以随时引用这些数据 你刚才告诉我 你正在开发的一款 App 使用了我们的许多新功能 其中包括地点 ID 你能否向我们展示一下 你正在构建的 App? 当然可以! 我的 App 主要用于在各个地点打卡 因此它能够从地点 ID 中获益 介绍一下我的 Apple Store 商店打卡程序 我喜欢游历各个国家或地区 在拥有最难得一见、最耀眼设计的 Apple Store 商店打卡 借助地点 ID 我想我可以 建立精确的商店索引 所有的打卡之旅都必须有一个起点 将 Apple Park Visitor Center 作为起始地点再合适不过了 为了开始建立索引 我需要找到 Visitor Center 的地点 ID 要查找地点 ID 我可以使用 现有的 API 如 Search 或 Geocoding 不过 我将使用 新的地点 ID 查找工具 因为这个工具在查找少量 ID 时 速度更快 我首先前往 developer.apple.com/cn/maps 并点按右上角的“资源”链接 在“资源”页面 我可以向下滚动 并找到“地点 ID 查找”!
在这里 我可以轻松搜索 Apple Park Visitor Center 或者直接在地图上轻点这个地点 查看它的地点 ID 太好了! 现在我获得了这个 ID 我想 在我 App 中的地图上显示这个地点 只需几行代码 我就能做到! 这就是我用来显示 Apple Park Visitor Center 的代码 在这里 我使用从查找工具中 获得的地点 ID 创建了一个标识符 这个标识符用于 通过异步请求获取地图项 App 直接显示带有一个标记的地图 这个标记包含上述项 通过使用地点 ID 而不是硬编码坐标 我将充分利用 Apple 地图编辑的力量 举例来说 这意味着 如果他们认为地点放错了位置 需要移动标记 我将继承他们的更改 而无需更新我的代码 我还可以与我的所有原生 App 和网页内容共享这个地点 ID! 这是我用来在网页上创建 等同显示效果的 JavaScript 代码 首先 我创建了一个 名为 entryPoint 的函数 我会告诉 MapKit JS 在初始化后调用这个函数 在这个函数中 我把 在“地点 ID 查找”网站上 找到的 ID 传递给一个新函数 这个函数会查找相应地点 并将结果发送给我的回调函数 在本例中就是 annotatePlace annotatePlace 函数会 以这个地点为中心构建一个新地图 然后添加一种专门用于 显示地点对象的新注释: PlaceAnnotation 接下来 我会告诉 MapKit JS 在加载后调用我的 entryPoint 函数 今年 初始化 MapKit JS 所需的代码 比以往任何时候都要少 首先 我在这个异步 script 标记中 添加 MapKit JS src 属性 并将我的 entryPoint 函数 列为 data-callback 这样 MapKit JS 就会 在加载后调用这个函数 最后一个属性是 data-token 但这个令牌来自何处? 今年 我们使用精简的 UI 取代了动态 JWT 生成流程 这个 UI 可生成 特定于域的令牌字符串 在你手动撤销之前 这些令牌字符串不会过期 我将使用新的令牌预置工具 创建一个新令牌! 我首先前往 developer.apple.com/cn/maps 并点按右上角的“资源”链接 在“资源”页面上 我可以向下滚动并找到“创建令牌”
通过点按添加按钮 我可以填写简短的表单 以创建一个 MapKit JS 生产令牌 这个令牌不会过期 而且只在我的域中有效 创建令牌后 如果它不再有效 我日后可以返回这里将它撤销 在此基础上 我可以将令牌 直接拷贝到我的 script 标记中 而不必考虑过期日期、 动态 JWT 生成 或再次调用 mapkit.init 看起来我已经做好准备 能够在我的 App 和网页上展示我的 Apple Store 商店新手引导工具了 你的 App 有了良好的开端! 能够在 App 和网站上 使用同一标识符 来引用单个 Apple Store 商店 这项功能非常有用 使用地点 ID 你可以实现许多可能 要进一步了解如何使用 这项技术来构建和维护 你自己的地点 ID 集合 请观看本讲座相关资源中的链接: “使用地点 ID 标识唯一地点” 使用地点 ID 你能够 获取自己所关注地点的最新信息 接下来将介绍什么主题呢? 在你的 App 或网站中显示地点信息 当你使用“地图”App 轻点 某个地点时 将显示地点卡 地点卡显示开放时间、 电话号码等信息 今年 MapKit 为你的 App 和网站 带来了地点卡功能 可使用新的 SelectionAccessory API 来实现一种效果 即每当在地图上 选择某个地点时 都能显示地点信息 信息可以全面铺陈以详尽呈现 也可以紧凑编排
以节省空间 甚至仅显示一个链接 以供在“地图”App 中查看地点 你可以根据自己的用例 选择最适合的样式 或者直接使用默认的自动样式 让 MapKit 选择最适合 平台和地图视图尺寸的样式 即使你的 App 或网站没有地图视图 你也可以显示地点卡 MapItemDetail API 和 PlaceDetail API 非常灵活 并支持多种用例 最后 嵌入是将地图 添加到网站的快捷方式 开发者网站上的 “创建地图”工具可以 直接生成 HTML 供你拷贝并粘贴 Mike 在他的 App 中引入了地点卡 给我们展示一下你的成果吧! 当然可以!现在我已经在地图上 显示了 Visitor Center 我希望美观地显示 关于它的最新信息! 这是我 App 中的一个地图 其中显示我到访过的所有商店 我只需添加一行代码即可! 现在 当我轻点 一家到访过的商店时 我的 App 就会显示 有关这家商店的大量详细信息 美观的地点数据 在任何地方都非常有价值 让我也更新一下之前的网站吧 我修改了之前的 MapKit JS 代码 增加了两行 第一行创建新的 PlaceSelectionAccessory 第二行将这个附件 附加到之前的现成注释 现在 选择 Apple Park Visitor Center 时 会弹出丰富的地点数据 如果页面包含多个兴趣点 或其他复杂使用模式 那么非常适合使用 MapKit JS 显示地点信息 但是如果网站只显示 单个地点的数据 我可以使用 Jeff 提到的新的 低代码解决方案:嵌入! 由于我的网站目前只显示了一家 Apple Store 商店的详细信息 我想知道如何通过嵌入 实现相同的功能 我首先前往 developer.apple.com/cn/maps 并点按右上角的“资源”链接 在“资源”页面上 向下滚动并找到“创建地图”
在这里 我可以选择 新增的“Web Embed”按钮 以使用我的地点 ID、开发者令牌等 对嵌入式地图进行自定 一旦地图呈现我想要的显示效果 我就可以快速将生成的 HTML 代码片段拷贝到我的网站 这样无需编写任何 JavaScript 代码 就能获得美观的地图! 显示丰富地点信息的最后一种方式 是完全脱离地图环境的 有时 我需要一个可快速滚动的列表 来显示我打卡过的每家商店 而不是地图 我认为每个列表项 都包含地点信息会很有帮助 这是我用来显示 收藏地点列表的代码 首先 我显示我收藏的所有商店列表 其中每个列表项 都会显示对应商店的名称 通过这行代码 现在轻点列表项时 将显示完整的地点卡! 我还希望在网页上 看到所收藏商店的详情 因此我还要更新我的网站 在这里 我利用了之前的网页代码 但是使用几条语句替换了 annotatePlace 函数 第一条语句从页面中抓取一个元素 第二条语句在这个元素中 填入我所查找地点的详情 我还使用了新的自适应配色方案 它使这些地点详情 能够匹配系统偏好的配色方案: 浅色或深色 现在也可以使用相同的值 来创建自适应 MapKit JS 地图 我的 App 和网站真的是相得益彰! 你觉得呢? 它们确实相得益彰! 你使用我们的新 API 来显示所打卡商店的 地点信息 即使你的 App 没有地图视图 也可以使用 MapItemDetail API 来显示地点卡 API 适用于 UIKit、 AppKit 和 SwiftUI 通过 MapKit JS 你可以使用 PlaceDetail API 将地点卡作为内容包含在网页中 可以使用 SelectionAccessory API 直接在地图中显示地点信息 MapKit 提供的 SelectionAccessory API 可用于各种平台和技术
看着地图上你收藏的所有商店 我也想跟随你的脚步到访每家商店 我想如果能带上我的女儿 让她们挑选第一件 Apple T 恤 一定很有意思 她们也喜欢愉快地去野餐 或许我们可以找个公园随便吃点什么 我有个主意 使用 SelectionAccessory API 你可以实现对地图上的 所有其他地点显示地点卡 而不仅仅是 Apple Store 商店 我可以在你收藏的 每家商店附近轻点一下 直到找到一个看起来不错的 公园进行野餐 这将帮助我选择最适合 我们到访的 Apple Store 商店! 让我们来看看 你的 App 针对代表所收藏商店的 每个地图项添加了一个标记 在 SwiftUI 中 你可以通过使用 MapSelection 支持选择自己的内容 和地图自身的地图功能 你可以在使用这个修饰符 选择地图功能时 显示地点卡标注 这是开始使用 SelectionAccessory API 的绝佳方法 只需在现有 App 代码中 为地图功能启用它即可 你的 App 可以像以前一样 显示注释内容 你的顾客可以在地图上进一步了解 你的内容附近的地点 这种额外的上下文 可以带来许多价值 现在你知道了如何使用 地点 ID 引用地点 并使用地点卡显示有用的信息 但是具体涵盖哪些地点呢? 如果你不是只展示 使用地点 ID 查找工具 找到的特定地点 你可能需要 让你的 App 支持搜索地点! 今年我们对 Search API 进行了改进 包括筛选搜索结果的新方法 你可以搜索更多类型的地点 比如音乐场所、 滑板公园甚至城堡 你可以搜索自然地理特征 如河流或山脉 你甚至可以搜索 地址的特定组成部分 如城市或邮政编码 我们实现了仅搜索特定 限定区域的搜索功能 这样你就可以只关注相关区域 我们还为 Server API 添加了分页功能 这样就可以搜索到大量结果 事实上 可以搜索到数页结果! 你认为这些新的搜索功能 可以帮助你更有效地 查找商店和进行打卡吗? 当然了! 花了一整天时间在湾区寻找 野外 Apple Store 商店后 任何打卡者可能都需要 喝点浓咖啡才能继续工作! 我将在我的网站上 使用这些新的搜索功能 以确保我可以提前规划 以便根据需要随时喝到咖啡 然后 我将在我的原生 App 中 添加相同的功能 首先 我需要找到 Cupertino 我似乎可以使用新的 AddressFilter 来实现这一点 在这里 我创建了一个 AddressFilter 以仅搜索地点 即大多数国家或地区的城市 然后 我将这个筛选器 插入一个新的 Search 对象 最后 我使用 Cupertino 一词 运行搜索以找到我正在寻找的城市 而不必担心可能会找到 一家具有以下名称的公司: Cupertino's Classy Cleaners 与 Cupertino 匹配的 城市搜索结果 会被发送到我设置的 showMap 函数 在这个函数中 我将第一个搜索结果 用于创建区域 这个区域将传入新地图 这些代码将 Cupertino 作为地图的中心 这正是我想要的效果 然后我在这个区域中 创建一个新的搜索 即搜索咖啡 最后 我为每个咖啡结果 添加一个新的 PlaceAnnotation 现在每个结果都标注在地图上! 但还有一个问题 当我缩小地图时 我发现我在地图中 初始视口之外的位置 添加了许多注释 我真的不需要所有这些 距离过远的咖啡店 因此我可以使用 RegionPriority 来移除它们 只需这一个属性 我就可以要求 将结果限定在传入搜索的区域内 完成这一更改后 页面上的结果仅限于 原始区域内的结果 这些结果仅包括 我通过骑行方式可以到达的地方 当然 原生 App 也可以 使用所有这些功能 我将以完全相同的方式 增强我的 App 在这里 我创建了一个视图 用于显示每家咖啡店的标记 我使用 findCity 以异步方式 获取城市区域 然后使用 findCoffee 查找这个区域内的咖啡店 这是 findCity 它使用 addressFilter 搜索地点 就像 JS 代码那样 这是 findCoffee 我们利用 Cupertino 区域 并使用 regionPriority 搜索必须位于这个区域内的 咖啡店 好了 我想有了这些改进 我一定可以成为最优秀的 Apple Store 商店打卡人员 我也这么认为!很期待看到 你的打卡之旅取得新进展 你甚至可以收集护照上的印章!
好了!我们已经介绍了很多内容! 借助最新版本的 MapKit 可以轻松使用地点 ID 来引用地点 使用地点卡显示有用的信息 甚至将地点嵌入网页 你可以使用它来搜索 许多不同类型的地点 还可以将搜索范围限定在特定区域 MapKit Server API 可通过分页功能 提供比以往更多的结果 MapKit JS 提供了 简化的令牌预置流程 让你可以更轻松地上手 在结束之前 让我给大家布置一些作业 使用地点 ID 来引用周六上午 举办农贸市场的公园 在你的 App 中使用地点卡 来显示地点信息 地点卡将为 App 搜索结果 以及对你至关重要的 特定地点提供深层次的信息 为地图上的所有兴趣点启用地点卡 为使用你的 App 探索某个区域的用户 提供有价值的上下文 务必访问这个链接: “使用地点 ID 标识唯一地点” 进一步了解如何使用地点 ID! 希望你喜欢 探索 MapKit 的新功能 欢迎继续绘制地图 我会继续打卡!
-
-
3:06 - Display a visitor center annotation
// Display a visitor center annotation 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 } } }
-
3:44 - Display an annotation for the center
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const center = place.coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const annotation = new mapkit.PlaceAnnotation(place); map.addAnnotation(annotation); }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
7:32 - Display my favorite apple stores
// Display my favorite apple stores 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() } } }
-
7:50 - Display a selectable annotation
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const center = place.coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const annotation = new mapkit.PlaceAnnotation(place); map.addAnnotation(annotation); const accessory = new mapkit.PlaceSelectionAccessory(); annotation.selectionAccessory = accessory; }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
9:15 - List stores and show details when selected
// List stores and show details when selected 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) } }
-
9:37 - Show visitor center details
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const el = document.getElementById("place"); const detail = new mapkit.PlaceDetail(el, place, { colorScheme: mapkit.PlaceDetail.ColorSchemes.Adaptive }); }; </script> <div id="place"></div> </body> </html>
-
11:17 - Display a place card for the selected map feature, too
// Display a place card for the selected map feature, too 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) } }
-
13:09 - Find Cupertino, then find coffee
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const addressFilter = mapkit.AddressFilter.including([ mapkit.AddressCategory.Locality ]); const citySearch = new mapkit.Search({ addressFilter }); citySearch.search("Cupertino", showMap); }; const showMap = (error, cities) => { const center = cities.places[0].coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const coffeeSearch = new mapkit.Search({ region, regionPriority: mapkit.Search.RegionPriority.Required, pointOfInterestFilter: mapkit.PointOfInterestFilter.including([ mapkit.PointOfInterestCategory.Cafe ]) }); coffeeSearch.search("coffee", (error, results) => { for (const place of results.places) { const marker = new mapkit.PlaceAnnotation(place); map.addAnnotation(marker); } }); }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
14:41 - Finding coffee in Cupertino
// Finding coffee in Cupertino 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 ?? [] } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。