版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.10.12 星期一 |
前言
MapKit框架直接从您的应用界面显示地图或卫星图像,调出兴趣点,并确定地图坐标的地标信息。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. MapKit框架详细解析(一) —— 基本概览(一)
2. MapKit框架详细解析(二) —— 基本使用简单示例(一)
3. MapKit框架详细解析(三) —— 基本使用简单示例(二)
4. MapKit框架详细解析(四) —— 一个叠加视图相关的简单示例(一)
5. MapKit框架详细解析(五) —— 一个叠加视图相关的简单示例(二)
6. MapKit框架详细解析(六) —— 添加自定义图块(一)
7. MapKit框架详细解析(七) —— 添加自定义图块(二)
8. MapKit框架详细解析(八) —— 添加自定义图块(三)
9. MapKit框架详细解析(九) —— 地图特定区域放大和创建自定义地图annotations(一)
10. MapKit框架详细解析(十) —— 地图特定区域放大和创建自定义地图annotations(二)
11. MapKit框架详细解析(十一) —— 自定义MapKit Tiles(一)
12. MapKit框架详细解析(十二) —— 自定义MapKit Tiles(二)
13. MapKit框架详细解析(十三) —— MapKit Overlay Views(一)
14. MapKit框架详细解析(十四) —— MapKit Overlay Views(二)
15. MapKit框架详细解析(十五) —— 基于MapKit和Core Location的Routing(一)
16. MapKit框架详细解析(十六) —— 基于MapKit和Core Location的Routing(二)
开始
首先看下主要内容:
在本
MapKit
教程中,您将学习如何使用Indoor Maps
来绘制建筑物内部的地图,在不同stories
之间切换以及查找建筑物内部的位置。内容来自翻译。
接着看下写作环境:
Swift 5, iOS 14, Xcode 12
苹果公司在iOS 13
中引入Indoor Maps to MapKit
。该程序提供了对物理结构内部进行地图绘制的工具,从而使用户可以从内部导航建筑物。
在本教程中,您将学习如何使用Indoor Maps
将RazeWare
办公室的位置添加到地图中。 在此过程中,您将学习:
- 将
Indoor Mapping Data Format (IMDF)
数据解析为模型。 - 在地图上从
GeoJSON
绘制几何。 - 样式图几何
(Style map geometry)
。 - 在结构的不同级别之间切换。
- 获取室内位置。
注意:本高级教程假定您可以轻松地使用
Swift
在Xcode
中构建iOS应用。 本教程使用MapKit
。 如果您不熟悉MapKit
,请先阅读MapKit Tutorial: Getting Started。
打开启动项目。 进行构建并运行以查看您的工作方式。
对于你们中观察较少的人,它是一张地图。
入门项目已经包含您要使用的用户界面,一些模型文件和IMDF
存档。现在,它不是一个鼓舞人心的应用程序;它肯定不会在应用程序审查中超越Apple Genius
。但是,您将改变这一点!
Understanding Indoor Maps
在开始编写代码之前,重要的是要知道您正在使用哪种数据。在本教程中,您将使用两个标准:GeoJSON
和IMDF
。
1. What Is GeoJSON?
GeoJSON
是一种用于表示地理数据结构的格式。顾名思义,GeoJSON
基于JSON
格式。互联网工程任务组(Internet Engineering Task Force (IETF))
于2015年发布了该文档,以标准化程序员对地理数据进行建模的方式。
GeoJSON
支持使用纬度和经度对定义的一系列不同的几何类型。您可以组合经纬度对以形成更复杂的结构。 GeoJSON
对象可以表示:
Geometry: A region of space
Feature: A spatially-bound entity
FeatureCollection: A list of Features
看一下上面地图中概述的建筑结构。 结构几何图形中的每个点都有一个纬度和经度坐标,就像您在笛卡尔坐标系Cartesian coordinate system中的x-y
位置一样。 每个点的标记为1
到6
,代表绘制顺序。 左侧的列显示每个点的实际经纬度坐标。
此功能的GeoJSON
表示形式如下:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"stroke": "#101889",
"stroke-width": 2,
"stroke-opacity": 1,
"fill": "#98a1e6",
"fill-opacity": 0.5
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-121.80389642715454,
37.33966009140741
],
[
-121.80312395095825,
37.338790026148
],
[
-121.80198669433592,
37.33937007077421
],
[
-121.80222272872923,
37.33960891137705
],
[
-121.80301666259766,
37.33966009140741
],
[
-121.80331707000731,
37.33998423078978
],
[
-121.80389642715454,
37.33966009140741
]
]
]
}
}
]
}
您可以自己重新创建此确切的结构。 转到geojson.io并将上面的GeoJSON
粘贴到右侧的编辑器中。 由于数组中的每个元素都是一个lat-long
对,因此结构将出现在与前面显示的图像相同的位置。
2. What Is IMDF?
苹果在iOS 13
中引入了Indoor Mapping Data Format (IMDF)
,作为对结构内部建模的新标准。 IMDF
存档包含一组要素类型,这些要素类型组合在一起可以描述室内空间,包括墙壁,门口,楼梯,水平等。 通常,您可以使用第三方软件来创建IMDF
存档,以将详细的平面图转换为IMDF
。
注意:创建IMDF存档超出了本教程的范围。 如果您想了解更多有关此的内容,这里有一个很棒的教程,Creating and Validating IMDF Datasets。
IMDF Feature Types
您已经知道IMDF
档案本质上是GeoJSON
文件的集合,当组合在一起时,它们描述了一个室内空间。 但是,这到底是如何工作的呢? 接下来,您将找到答案。
在Xcode
中,打开Project navigator
,然后展开IMDF/Data
。 每个GeoJSON
文件的名称都与IMDF
文档中描述的要素类型相对应。
例如,occupant.geojson
是Occupant types
的集合,building.geojson
是Building types
的集合,依此类推。 就这么简单。
您将在本教程中看到的主要类型是:
Venue
Unit
Occupant
Amenity
Level
现在您已经了解了将要使用的工具,是时候将它们投入使用了!
3. Using the GeoJSON Format
在项目导航器中打开occupant.geojson
。 您会发现GeoJSON
和IMDF
确实没有听起来那么可怕。
看一下列表中的其中一位occupants
:
{
"feature_type": "occupant",
"geometry": null,
"id": "5ac4bf40-2dbf-4bdb-9c39-495c442b7e39",
"properties": {
"address_id": null,
"alt_name": null,
"anchor_id": "76336b53-81c9-4c93-8f81-a4c008d54ba1",
"category": "office",
"display_point": {
"coordinates": [
-121.889609,
37.329678
],
"type": "Point"
},
"hours": "Su-Sa 09:00-17:00",
"name": {
"en": "Ray's Office"
},
"phone": "+14087924512",
"restriction": null,
"website": null,
"correlation_id": null
},
"type": "Feature"
}
这位occupant
描述了Ray
的办公室。一个有效的occupant
必须定义一个id,feature_type必须具有一个乘员值,并且其几何值必须为null。
occupant
不需要自己的几何图形,因为它使用来自anchor_id
在properties
内引用的锚点的几何图形。
仍在occupant.geojson
中,复制第一个occupant
的anchor_id
,然后打开anchor.geojson
并搜索id
。您会在其中找到关联的锚点功能。
properties
中的所有其他键都是描述occupant
的元数据。在本文中,您唯一要使用的密钥是name
,但在实际情况下,如果该信息与您的应用有关,则可以扩展它以显示营业时间,电话号码和网站。
另外,那不是Ray
的真实电话号码,所以不要打扰。
注意:您已经介绍了有关
GeoJSON
和IMDF
的一些顶级理论,但还有很多要了解的知识。 Apple的文档提供了IMDF
和不同功能类型的详细概述。 GeoJSON官方网站GeoJSON website上有一些很好的信息,但这是非常技术性的。学习的好方法是使用geojson.io上的工具进一步探索
GeoJSON
。您可以直接在地图上绘制几何图形,并使其输出关联的JSON
。
接下来,研究如何将GeoJSON
文件转换为可以在代码中使用的实际Swift
模型。
Modeling GeoJSON
在Project navigator
中打开IMDF/Models
。 您会注意到,您已经有很多需要入门的IMDF
功能类型,但是其中一种是Venue
类型。 您现在将添加它。
在Models
中创建一个新文件,将其命名为Venue.swift
并添加以下内容:
class Venue: Feature {
struct Properties: Codable {
let category: String
}
var levelsByOrdinal: [Int: [Level]] = [:]
}
这就是定义Venue
所需的全部代码。 看起来有点少,不是吗? Venue
与项目中的大多数其他模型类型一起从Feature
继承,Feature
是一泛类,该类具有通用类型Properties
,类型受限于Decodable
。
Feature
定义了每个IMDF feature
共有的id
和geometry
。 GeoJSON
数据内的Properties
对象可以是任何有效的JSON
对象。 每个功能都定义了自己的Properties
,如您在Venue
模型中所见。
Venue.Properties
作为通用类型参数传递。 它符合Codable
,因此满足Properties
的Decodable
的要求。
构建并运行。 您还看不到任何变化,但是距离室内地图仅一步之遥。
Decoding GeoJSON With MKGeoJSONDecoder
如果没有提供读取数据的方式来创建这种新的数据格式,Apple会感觉不太好。 在本部分中,您将使用MKGeoJSONDecoder
解码GeoJSON
数据。 MKGeoJSONDecoder
提供了一种使用decode(_ :)
将GeoJSON
解码为MapKit
类型的方法。
打开IMDF / IMDFDecoder.swift
。 您已经具有对某些类型进行解码的代码,但是您需要添加一个decode(_ :)
方法以将所有这些绑定到一个Venue
对象中。
在IMDFDecoder.swift
的顶部,添加以下方法:
func decode(_ imdfDirectory: URL) throws -> Venue {
// 1
let archive = Archive(directory: imdfDirectory)
// 2
let venues = try decodeFeatures(Venue.self, from: .venue, in: archive)
let levels = try decodeFeatures(Level.self, from: .level, in: archive)
let units = try decodeFeatures(Unit.self, from: .unit, in: archive)
let openings = try decodeFeatures(Opening.self, from: .opening, in: archive)
let amenities = try decodeFeatures(Amenity.self, from: .amenity, in: archive)
// 3
if venues.isEmpty {
throw IMDFError.invalidData
}
let venue = venues[0]
venue.levelsByOrdinal = Dictionary(grouping: levels) { level in
level.properties.ordinal
}
// 4
let unitsByLevel = Dictionary(grouping: units) { unit in
unit.properties.levelId
}
let openingsByLevel = Dictionary(grouping: openings) { opening in
opening.properties.levelId
}
// 5
for level in levels {
if let unitsInLevel = unitsByLevel[level.id] {
level.units = unitsInLevel
}
if let openingsInLevel = openingsByLevel[level.id] {
level.openings = openingsInLevel
}
}
// 6
let unitsById = units.reduce(into: [UUID: Unit]()) { result, unit in
result[unit.id] = unit
}
// 7
for amenity in amenities {
guard let pointGeometry = amenity.geometry[0] as? MKPointAnnotation
else { throw IMDFError.invalidData }
if let name = amenity.properties.name?.bestLocalizedValue {
amenity.title = name
amenity.subtitle = amenity.properties.category.capitalized
} else {
amenity.title = amenity.properties.category.capitalized
}
for unitID in amenity.properties.unitIds {
let unit = unitsById[unitID]
unit?.amenities.append(amenity)
}
amenity.coordinate = pointGeometry.coordinate
}
// 8
try decodeOccupants(units: units, in: archive)
return venue
}
相当多的代码,但是您会发现它并不太复杂。简而言之,您将IMDF
文件解码到一个venue
中,其中包含有关其内部的高度,单位,开口和便利设施的信息。
查看代码,正在发生的事情:
- 1)
archive
是用于访问完整IMDF
存档的容器。它需要项目中IMDF
存档的URL
,并且可以单独访问每个GeoJSON
文件。在这里,您创建一个Archive
实例。 - 2) 使用
decodeFeatures(_:from:in :)
,可以将feature type
解码为模型。 - 3) 如果
venues
是空的,则抛出错误。否则,请按Level
中的ordinal
属性对级别进行分组,然后将结果的Dictionary
分配给venue
中的levelsByOrdinal
。 - 4) 通过将
units
和openings
按其levelId
分组来创建两个新的Dictionary
对象。 - 5) 对于每个级别,使用在第四步中创建的两个Dictionary对象添加单位和空缺。
- 6) 按
ID
对units
进行分组。 - 7) 为每个
Amenity
设置title, subtitle
和coordinate
。对于任何关联的Unit
,将Amenity
添加到amenities
数组。 - 8) 解码完其他所有内容后,请调用
encodeOccupants(units:in :)
对occupants
进行解码。
1. Decoding the Archive
现在,您的解码方法已启动并正在运行,您需要从主视图控制器中调用它。
打开MapViewController.swift
。在var mapView
的声明下,添加以下代码:
let decoder = IMDFDecoder()
var venue: Venue?
在这里,您将准备一个IMDFDecoder
以及一个用于存储解码后的venue
的属性。
接下来,在setupMapView()
下,添加以下新方法:
func loadRazeHQIndoorMapData() {
guard let resourceURL = Bundle.main.resourceURL else { return }
let imdfDirectory = resourceURL.appendingPathComponent("Data")
do {
venue = try decoder.decode(imdfDirectory)
} catch let error {
print(error)
}
}
此方法从应用程序的资源包中加载Data
目录,然后使用您在上一步中创建的decode(_:)
将其解码到venue
中。
现在,您需要一种方法,通过缩放到地图上的位置来突出高亮您的venue
。 在loadRazeHQIndoorMapData()
之后,添加以下代码以创建一个名为showDefaultMapRect()
的新方法:
func showDefaultMapRect() {
guard
let venue = venue,
let venueOverlay = venue.geometry[0] as? MKOverlay
else { return }
mapView.setVisibleMapRect(
venueOverlay.boundingMapRect,
edgePadding: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20),
animated: false
)
}
此方法使用venue
几何体中的boundingMapRect
在mapView
上调用setVisibleMapRect(_:edgePadding:animated :)
。
boundingMapRect
包含几何图形中最小的可见地图矩形。 setVisibleMapRect(_:edgePadding:animated :)
的edgePadding
参数是地图矩形在屏幕点周围的额外空间。
最后,您需要调用这两个新方法。 在viewDidLoad()
的底部添加。
loadRazeHQIndoorMapData()
showDefaultMapRect()
构建并运行查看结果
欢迎来到RazeWare
总部!至少,它的位置。仍然没有可见的结构,但是随着地图的放大,您必须具有有效的Venue
。
Rendering Geometry on a Map
现在您可以使用本机模型类型了,现在该进行制图了!
MapKit
将MKOverlay
对象与MKOverlayRenderer
结合使用以在地图上绘制叠加层。
MKOverlay
是一种协议,用于描述叠加层的几何形状。 MapKit
包含采用此协议定义各种形状的各种具体类,例如矩形,圆形和多边形。
MKOverlayRenderer
是一个绘制MKOverlay
视觉表示的对象。
1. Adding Overlays
打开MapViewController.swift
并向下滚动到extension MapViewController:MKMapViewDelegate
。您会注意到这里已经定义了一些方法。
每当需要在地图视图上绘制叠加层时,MapKit
都会调用mapView(_:rendererFor :)-> MKOverlayRenderer
。此方法负责将适当的MKOverlayRenderer
传递回MapKit
。
目前,它做的并不多,但是接下来您将对其进行更改。
用以下代码替换整个方法:
func mapView(
_ mapView: MKMapView,
rendererFor overlay: MKOverlay
) -> MKOverlayRenderer {
guard
let shape = overlay as? (MKShape & MKGeoJSONObject),
let feature = currentLevelFeatures
.first( where: { $0.geometry.contains( where: { $0 == shape }) })
else { return MKOverlayRenderer(overlay: overlay) }
let renderer: MKOverlayPathRenderer
switch overlay {
case is MKMultiPolygon:
renderer = MKMultiPolygonRenderer(overlay: overlay)
case is MKPolygon:
renderer = MKPolygonRenderer(overlay: overlay)
case is MKMultiPolyline:
renderer = MKMultiPolylineRenderer(overlay: overlay)
case is MKPolyline:
renderer = MKPolylineRenderer(overlay: overlay)
default:
return MKOverlayRenderer(overlay: overlay)
}
feature.configure(overlayRenderer: renderer)
return renderer
}
在委派MKMapView
上调用addOverlays(_ :)
时,将调用此方法。提供给addOverlays(_ :)
的叠加层。
如您先前所学,MKOverlay
是具体类用来创建基本形状的协议。因此,在上面的代码中,您将检查其类型,并根据结果创建适当的MKOverlayRenderer
子类。
最后,在返回创建的渲染器之前,请在feature
上调用configure(overlayRenderer :)
。这是StylableFeature
中的方法。
您将在本教程的后面部分中确切地看到StylableFeature
是什么。
但是首先,您必须学习如何在叠加层旁边向地图添加注释(annotations)
。
2. Adding Annotations
您添加注释的方式与覆盖图类似。 MKMapViewDelegate
有一个委托方法mapView(_:viewFor :)-> MKAnnotationView ?
,每次在委派MKMapView
上调用addAnnotations(_ :)
时都会调用该方法。
就像叠加层一样,只有在向地图添加注释后,该方法才会被调用。目前,这还没有发生-但现在是您改变这种情况的时候了。
在showDefaultMapRect()
下面添加以下方法:
private func showFeatures(for ordinal: Int) {
guard venue != nil else {
return
}
// 1
currentLevelFeatures.removeAll()
mapView.removeOverlays(currentLevelOverlays)
mapView.removeAnnotations(currentLevelAnnotations)
currentLevelAnnotations.removeAll()
currentLevelOverlays.removeAll()
// 2
if let levels = venue?.levelsByOrdinal[ordinal] {
for level in levels {
currentLevelFeatures.append(level)
currentLevelFeatures += level.units
currentLevelFeatures += level.openings
let occupants = level.units.flatMap { unit in
unit.occupants
}
let amenities = level.units.flatMap { unit in
unit.amenities
}
currentLevelAnnotations += occupants
currentLevelAnnotations += amenities
}
}
// 3
let currentLevelGeometry = currentLevelFeatures.flatMap {
feature in
feature.geometry
}
currentLevelOverlays = currentLevelGeometry.compactMap {
mkOverlay in
mkOverlay as? MKOverlay
}
// 4
mapView.addOverlays(currentLevelOverlays)
mapView.addAnnotations(currentLevelAnnotations)
}
该方法的主要职责是拆除现有的叠加层和注释,并在其位置绘制新的叠加层和注释。 它采用一个单一的参数,即ordinal
,该参数指定获取场地几何图形的场所中的哪个级别。
细分:
- 1) 从地图上清除所有现有的注释和叠加层,并从本地缓存中删除关联的对象。
- 2) 对于选定的级别,获取关联的
features
并将其存储在您刚清空的本地数组内。 - 3) 创建两个数组。 第一个包含所选级别的
units
和occupants
的几何形状。 然后,使用compactMap(_ :)
创建一个MKOverlay
对象数组。 - 4) 在
mapView
上调用addOverlays(_ :)
和addAnnotations(_ :)
将注释和叠加层添加到地图上。
要查看运行中的代码,请在viewDidLoad()
的底部添加:
showFeatures(for: 1)
构建并运行。 尝试单击地图上的注释以查看当前的features
。
您现在是真正的制图师!
现在,您的地图可以正常运行,并按您的意愿显示RazeWare
办公室。 但是,它看起来并不理想。 在下一步中,您将自己的样式添加到地图中。
Styling Map Geometry
您已经为几何图形处理了很多样式,但是仍然看到两个默认的地图钉。 您需要将其替换为更时尚的东西。
在本部分中,您将学习StyleableFeature
的功能以及如何使用它来设置Occupant
模型类型。
StylableFeature
是示例项目(不是MapKit
)提供的协议,该协议定义了两种方法,一致的类型应采用这些方法来提供功能的自定义样式。
打开IMDF / StylableFeatures.swift
以查看内容。
protocol StylableFeature {
var geometry: [MKShape & MKGeoJSONObject] { get }
func configure(overlayRenderer: MKOverlayPathRenderer)
func configure(annotationView: MKAnnotationView)
}
extension StylableFeature {
func configure(overlayRenderer: MKOverlayPathRenderer) {}
func configure(annotationView: MKAnnotationView) {}
}
您会注意到,这两种configure
方法均默认为空实现,因为它们不是必需的。 一会儿您会明白为什么。 唯一的其他必须实现的是,合格的对象必须提供一个几何对象数组,这些对象从MKShape
继承并符合MKGeoJSONObject
。
打开IMDF / Models / Occupant.swift
并将以下扩展名添加到文件的底部,在大括号后:
extension Occupant {
private enum StylableCategory: String {
case play
case office
}
}
StylableCategory
定义了occupant.geojson
中使用的两种类型。
打开occupant.geojson
并查看两个对象的category
键。 您会注意到其中包含play
和office
。 这些类型是完全任意的,可以是GeoJSON
的作者选择的任何类型。 您可以根据需要创建任意多个这些类别。
继续在Occupant.swift
中,将下一个扩展名添加到文件的底部,在大括号后:
extension Occupant: StylableFeature {
func configure(annotationView: MKAnnotationView) {
if let category = StylableCategory(rawValue: properties.category) {
switch category {
case .play:
annotationView.backgroundColor = UIColor(named: "PlayFill")
case .office:
annotationView.backgroundColor = UIColor(named: "OfficeFill")
}
}
annotationView.displayPriority = .defaultHigh
}
}
现在,Occupant
符合StylableFeature
并实现了configure(annotationView:MKAnnotationView)
。 此方法检查category
是否为有效的StylableCategory
,并根据该值返回颜色。 如果一个叠加层与另一个叠加层发生碰撞,则displayPriority
最低的叠加层将被隐藏。
构建并运行。 您会看到这些可怕的默认地图图钉已消失。 取而代之的是两个具有自定义颜色的地图点。
Selecting Levels
您已经遇到过几次Level
。 它描述建筑物的一个级别或story
,并且可以具有自己的一组功能。 以一个购物中心为例:他们通常有多个story
,每个楼层都有不同的商店。 使用Level
,您可以描述每个楼层上的哪些商店。
接口的分段控件已经到位,并且已经连接到segmentedControlValueChanged(_ sender :)
。 您还可以基于带有showFeatures(for :)
的Level
重绘几何。 您一切顺利!
打开MapViewController.swift
并将以下代码添加到segmentedControlValueChanged(_ sender :)
的主体中:
showFeatures(for: sender.selectedSegmentIndex)
构建并运行。 当您在分段控件上更改级别时,地图叠加层将重新绘制提供的级别。
在最后一节中,您将学习如何使用室内位置来查看办公室内的位置。
Using Location With Indoor Maps
您可能会想,“ GPS
如何在室内工作!” 简单的答案是:否。 至少不好。
室内定位实际上根本没有使用GPS
。 苹果使用iOS
设备内部的传感器根据方向,行进速度和射频“指纹”来确定您的室内位置。
固定的Wi-Fi
接入点在建筑物内部发出可追踪的频率模式。 这些独特的信号是使用Apple的Indoor Survey应用收集的。 打开Indoor Survey
应用程序后,您便可以在场所四处走动,同时收集频率和信号。 这从内部映射您的建筑物。
不用担心,您不必飞到加利福尼亚州并在本文中划定办公室的位置-也许下次。
将下面的变量添加到MapViewController.swift
的let decoder = IMDFDecoder()
顶部:
let locationManager = CLLocationManager()
现在,在segmentedControlValueChanged(_ sender :)
上方添加以下方法:
func startListeningForLocation() {
locationManager.requestWhenInUseAuthorization()
}
最后,将以下代码添加到viewDidLoad()
的底部:
startListeningForLocation()
使用上面的代码,您将在加载视图控制器时开始侦听位置更改。
为此,您需要在模拟器中启用位置服务并添加自定义位置。 将模拟器置于前台,导航至Features ▸ Location ▸ Custom Location
。 在出现的对话框中,输入以下内容:
37.329422 for Latitude
-121.887861 for Longitude
构建并运行。 模拟器将要求位置许可-确保您授予它!
现在,您已经了解了IMDF
的基础知识,但还有很多东西要学习。 尝试在本教程中使用GeoJSON
来构建自己的地图几何。 尝试添加楼层,更改名称和自定义颜色。
geojson.io是开始创建自己的GeoJSON
的好地方。 尝试在地图上绘制形状,然后在此项目的IMDF
存档中使用它们。
如果您想将MapKit
的知识提高到一个新的水平,请尝试我们的专业课程:MapKit and CoreLocation。
确保您还查看了WWDC19
上有关室内地图的两个讲座: What’s New in MapKit and MapKitJS以及Adding Indoor Maps to your App and Website。
后记
本篇主要讲述了基于
MapKit
使用Indoor Maps
来绘制建筑物内部的地图的简单示例,感兴趣的给个赞或者关注~~~