MapKit 是 Apple 提供的一个框架,用于在 iOS、macOS、watchOS 和 iPadOS 应用中嵌入地图视图,支持地图显示、导航、地理编码等功能。
本文主要使用 Swift + MapKit 实现地图显示,与之前的 UIKit + MapKit 实现的方式不同,SwiftUI 的 MapKit 是现代化的封装,旨在提升开发效率,但功能还不够完善,某些复杂功能可能依赖 UIKit 实现。
主要功能
1、地图显示
在应用中嵌入交互式地图。
支持平移、缩放、旋转和俯视等多种手势操作。
添加自定义标注(Annotations)和覆盖物(Overlays),如地理区域、高亮路径等。
3、地理编码和反向地理编码
将地址转换为地理坐标(纬度和经度)。
将地理坐标转换为地址。
4、导航和方向
计算两点之间的路线。
支持步行、驾车和骑行等多种导航模式。
5、地点搜索
搜索附近的兴趣点(POI),如餐馆、加油站等。
显示地图
// 导入框架
import MapKit
struct ContentView: View {
var body: some View {
// SwiftUI视图放置地图
Map()
}
}
修改地图外观
Map()
.mapStyle(.imagery)
// 创建一个3D地图
Map()
.mapStyle(.hybrid(elevation: .realistic))
MapStyle地图样式
1、hybrid
表示混合视图,包括卫星图像和道路名称的叠加。
2、imagery
表示纯卫星视图(没有任何叠加信息)。
3、standard
表示标准地图视图(包括道路、地标和其他兴趣点信息)。
可选参数
某些样式(如 hybrid 和 standard)可能还提供可选参数,用于自定义地图样式的细节,例如:
1)elevation
是否显示地形。
.automatic:该选项表示自动选择地形样式,由系统根据设备性能、用户设置和当前环境决定是使用 2D 还是 3D 样式。
.flat:使用平面化的 2D 样式,即不渲染任何真实的地形高度或深度。
.realistic:使用真实的 3D 地形样式,包括山脉、丘陵、峡谷等自然地形的真实高度和深度。
示例:
Map()
.mapStyle(.hybrid(elevation: .realistic))
2)pointsOfInterest
是否显示兴趣点。
.all:显示所有兴趣点。
.including(_:):仅显示指定类别的兴趣点。
.excluding(_:):显示所有兴趣点,除了某些指定类别的兴趣点。
.excludingAll:隐藏所有兴趣点。
代码示例:
Map()
// 兴趣点显示只银行
.mapStyle(.standard(pointsOfInterest: .including(.bank)))
常见的兴趣点枚举值:
.restaurant: 餐厅
.cafe: 咖啡馆
.museum: 博物馆
.bank: 银行
.hotel: 酒店
.gasStation: 加油站
.groceryStore: 超市
3)showsTraffic
是否显示交通信息。
true: 地图会显示交通拥堵、车流等信息;
false: 不会显示。
限制操作
interactionModes
作用是初始化一个 Map 视图,并限制用户可以对地图进行的交互操作。
// 允许用户旋转(.rotate)和缩放(.zoom)地图
Map(interactionModes: [.rotate, .zoom])
MapInteractionModes 中其他常见选项:
.all:启用所有交互模式,包括旋转、缩放和平移。
.pan:允许用户平移(拖动)地图。
.pitch:允许用户改变地图的倾斜角度
.rotate:允许用户旋转地图
.zoom:允许用户缩放地图
示例:
1、启用所有交互模式:
Map(interactionModes: .all)
用户可以自由平移、缩放和旋转地图。
2、仅允许平移:
Map(interactionModes: [.pan])
用户只能拖动地图,无法缩放或旋转。
3、禁用所有交互模式:
Map(interactionModes: [])
用户无法进行任何交互,地图显示为静态。
MapCameraPosition
MapCameraPosition 是 SwiftUI 中更现代和灵活的地图位置管理方式。
let position = MapCameraPosition.region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275),
span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
)
)
var body: some View {
Map(initialPosition: position)
.edgesIgnoringSafeArea(.all)
}
它相比传统的 MKCoordinateRegion 提供了更多功能,适合用于高级地图交互和 3D 显示。
相关知识可以进一步阅读《Swift控制地图类型MapCamerPosition》
注意:
CLLocationCoordinate2D(latitude:longitude:) 的经纬度值必须合法:
纬度范围:-90 到 90
经度范围:-180 到 180
关于经纬度知识可以查看《地图知识《经纬度》》这篇文章。
CLLocationCoordinate2D
CLLocationCoordinate2D 是一个结构体,用来表示一个地理坐标点(经纬度)。
struct CLLocationCoordinate2D {
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
}
latitude: 纬度,表示地理位置的南北方向。
longitude: 经度,表示地理位置的东西方向。
类型为 CLLocationDegrees(实际上是 Double 类型)。
let coordinate = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
// 表示美国旧金山的地理坐标:纬度 37.7749°,经度 -122.4194°
MKCoordinateSpan
MKCoordinateSpan 是一个结构体,用来表示地图显示范围的跨度(东西和南北方向的覆盖范围)。
struct MKCoordinateSpan {
var latitudeDelta: CLLocationDegrees
var longitudeDelta: CLLocationDegrees
}
latitudeDelta: 纬度跨度,表示地图南北方向的覆盖范围。
longitudeDelta: 经度跨度,表示地图东西方向的覆盖范围。
单位是 度数(degrees)。
跨度越小,地图显示的区域范围越小,放大效果更明显;跨度越大,地图显示的区域范围越大,缩小效果更明显。
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
// 表示地图将显示一个南北和东西方向分别为 0.1 度范围的区域
Swift实际应用
动态切换MapCameraPosition.region,改变地图的显示区域:
import SwiftUI
import MapKit
struct ContentView: View {
@State private var position = MapCameraPosition.region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275),
span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
)
)
var body: some View {
Map(position: $position)
.onMapCameraChange { context in
print(context.region)
}
HStack(spacing: 50) {
Button("Paris") {
position = MapCameraPosition.region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
)
)
}
Button("Tokyo") {
position = MapCameraPosition.region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 35.6897, longitude: 139.6922),
span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
)
)
}
}
}
}
Map组件修饰符
在上面的代码中涉及onMapCameraChange修饰符,用于监听地图相机位置的变化,onMapCameraChange监听的内容为:
MKCoordinateRegion(center: __C.CLLocationCoordinate2D(latitude: 48.85660000000001, longitude: 2.3522000000000536), span: __C.MKCoordinateSpan(latitudeDelta: 1.2305118256316732, longitudeDelta: 1.0004438526408777))
onMapCameraChange 是 SwiftUI 中 Map 组件的修饰符,用于监听 地图相机位置的变化。它可以捕获地图显示区域、中心点、缩放级别等信息在变化时触发的事件。常见场景包括用户交互(如拖动地图、缩放)或程序性更新地图视图。
onMapCameraChange主要有三种用法:
// 交互结束后触发
.onMapCameraChange { context in
print(context.region)
}
// 交互结束后触发,与上面等同
.onMapCameraChange(frequency: .onEnd) { context in
print(context.region)
}
// 相机更新时触发,频率更高,如实时更新
.onMapCameraChange(frequency: .continuous) { context in
print(context.region)
}
初始地图的用法
1、Map(initialPosition: position)
Map(initialPosition: position)
initialPosition:这是一个只读的参数,用来设置地图的初始显示位置。它会在视图第一次加载时设置地图的初始坐标和缩放级别(通过 MapCameraPosition 或 MKCoordinateRegion)。
不会更新:这个参数仅在视图初始化时生效,一旦地图加载完成,initialPosition 不会再有任何变化。如果想动态更新位置,initialPosition 不会起作用。
用途:适用于只需要设置地图的初始显示位置,并且不打算根据用户的操作或者其他因素动态更新地图的显示位置。
2、Map(position: $position)
Map(position: $position)
position:这里的 position 是一个可变绑定(Binding),即地图的视角和位置会与 position 变量保持同步。每当 position 更新时,地图视图会自动重新渲染并更新位置。
动态更新:通过 $position 传递绑定,意味着地图的坐标和视角可以根据代码中的更新动态改变。例如,可以通过用户操作、数据变化或手动更新 position 来改变地图视角。
用途:适用于需要动态控制地图位置的情况,比如根据用户的交互(如拖动、缩放)或外部因素(如位置更新)来改变地图显示的区域。
关键区别
初始位置 vs 可变位置:initialPosition 只用于设置初始显示位置,并且不会随着数据更新而改变。而 $position 是一个双向绑定,使得地图的视角和代码中的 position 保持同步,能够动态更新。
静态 vs 动态:initialPosition 适用于只设置一次初始位置,$position 适用于需要不断更新的位置或视角。
示例:
使用 initialPosition 设置初始位置:
struct ContentView: View {
let position = MapCameraPosition.region(
MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
)
var body: some View {
Map(initialPosition: position) // 设置地图的初始位置
.edgesIgnoringSafeArea(.all)
}
}
这段代码会设置一个初始位置,但如果之后改变 position,地图的位置不会更新。
使用 $position 绑定位置:
struct ContentView: View {
@State private var position = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
var body: some View {
Map(coordinateRegion: $position) // 绑定位置
.edgesIgnoringSafeArea(.all)
}
}
这里,$position 是一个绑定(Binding),意味着当 position 更新时,地图会自动反映这些更新。如果更改 position(例如响应用户交互或程序内部逻辑),地图会立即显示新的位置。
Map标记地标
Marker标记
在Map中可以通过Marker显示地标名称并定位到相应的坐标。
import SwiftUI
import MapKit
struct Location: Identifiable {
let id = UUID()
var name: String
var coordinate: CLLocationCoordinate2D
}
struct ContentView: View {
let locations = [
Location(name: "Buckingham Palace", coordinate: CLLocationCoordinate2D(latitude: 51.501, longitude: -0.141)),
Location(name: "Tower of London", coordinate: CLLocationCoordinate2D(latitude: 51.508, longitude: -0.076))
]
var body: some View {
Map {
ForEach(locations) { location in
Marker(location.name, coordinate: location.coordinate)
}
}
}
}
Map的初始化方法支持闭包来定义地图的内容,因此可以在Map中使用ForEach循环。
关于Marker更多的知识,可以阅读《SwiftUI地图标记点Marker》
Annotation标记
Annotation 是 SwiftUI 和 MapKit 提供的一种在地图上标记位置的方式。与 Marker 类似,它用于在地图上展示特定位置,但更灵活,可以自定义样式、内容和交互。
Annotation(location.name, coordinate: location.coordinate) {
VStack {
Image(systemName: "mappin.circle.fill")
.foregroundColor(.red)
Text(location.name)
.font(.caption)
.padding(4)
.background(Color.white)
.cornerRadius(8)
}
}
关于Annotation更多的知识,可以阅读《SwiftUI地图标记点Marker》
MapReader视图
MapReader { proxy in
Map()
.onTapGesture { position in
if let coordinate = proxy.convert(position, from: .local) {
print(coordinate)
}
}
}
通过MapReader包装后,可以实现点击获取地图的实际位置。
关于MapReader更多的知识,可以阅读《SwiftUI获取地图信息的MapReader视图》
常见应用场景
1、显示用户当前位置。
2、标注地点(如商店、餐馆、景点)。
3、显示路径或路线导航。
4、搜索附近的兴趣点。
5、结合 CoreLocation 实现地理围栏或实时位置跟踪。
总结
MapKit 是一个强大的地图框架,适用于需要嵌入地图和地理功能的应用。通过结合 SwiftUI 的 Map 组件,开发者可以快速构建现代化的交互式地图界面,同时支持自定义标注和导航功能。
参考文章
Integrating MapKit with SwiftUI:https://www.hackingwithswift.com/books/ios-swiftui/integrating-mapkit-with-swiftui