这是我投稿【
2024年创作者训练营第四期】首篇文章,也希望借此机会作为动力,加快自己的学习进度!
前言
主要就是自己在学习的过程中发现目前很多关于 MapKit的讲解教程中使用的 API 大多数都是在 ios17 已经废弃,所以自己想梳理下当前框架的使用,有问题地方欢迎指出 👋!
你可能已经知道但是可能忘记的知识点:经度纬度 & 解密
| 经度 | 纬度 |
|---|---|
当前文章主要讲解
MapKit在SwiftUI中的使用场景,在AppKit中的使用持续更新中...
概述
MapKit主要作用就是在您的应用程序中显示地图或卫星图像,你可以跟地图上的位置进行交互(例如:缩放,旋转,拖拽,调整俯仰视角),通过交互你可以在地图上获得你想要的信息。
使用 MapKit 为您的应用程序提供地图和位置信息的位置感。您可以使用 MapKit 框架来:
- 将地图直接嵌入到应用程序的窗口和视图中。
- 向地图添加注释和叠加层以标出兴趣点。
- 响应用户与众所周知的兴趣点、地理特征和边界的交互。
- 提供文本补全功能,以便用户轻松搜索目的地或兴趣点。
MapKit for SwiftUI允许您利用从卫星图像以及丰富的3D透视图像的地图样式来呈现生动的地图。使用MapContentBuilder,您可以配置地图以显示标记和注释视图,或者对于更专业的内容,您可以设计自己的 SwiftUI 视图以放置在地图上。为了增加更多的交互性,MapKit for SwiftUI支持叠加来突出显示地图上的区域,使您能够使用MapPolyline制作路径和方向的动画,或者让人们可以轻松地通过可点击的兴趣点更深入地挖掘地面细节。使用您的应用程序的用户还可以使用LookAroundPreview和Look around查看器在街道层面进行探索。
我这边主要从以下五个角度去进行讲解当前 MapKit的具体使用.
地图构造器 MapContentBuilder
首先我们可以通过 Xcode查看到目前MapKit的API文档,显示之前的接口已经标注在 ios17 废弃,请使用 MapContentBuilder,我们可以先看看新的 API 中 MapcontentBuilder 倒地是怎么样的。
通过上图标注我们可以知道 MapContentBuilder 是 Map 函数的尾随闭包,且其闭包的返回值为泛型 C,C遵循MapContent协议。
所以其构造器除了构造器前参数外,其尾随闭包MapContentBuilder内支持的 SwiftUI 符合 MapContent协议类型的 SwiftUI组件。符合其协议的 SwiftUI 有如下,都可以在其构造器中使用!
- Annotation 用户可以自定标注内容的标注地图位置
- MapCircle:以地理坐标为中心,可配置半径的圆形图像叠加图。
- MapPolygon:封闭多边形叠加图
- MapPolyline:由一条或多条 [相连线段] 组成的开放多边形叠加图
- UserAnnotation:表示和管理地图上用户当前位置的标注
- Marker:标记地图位置的气球状标注。
现在我们示例代码演示如何使用 MapContentBuilder 构造器闭包创建地图上的标注:
- 首先我们定义下位置坐标信息
**extension** CLLocationCoordinate2D {
/* 故宫坐标 latitude: 纬度, 经度:longitude*/
static let palaceMuseum = CLLocationCoordinate2D(latitude: 39.9163, longitude: 116.3972)
/* 漓江坐标1 */
static let liRiver1 = CLLocationCoordinate2D(latitude: 25.2740, longitude: 110.2901)
/* 漓江坐标2 */
static let liRiver2 = CLLocationCoordinate2D(latitude: 25.2740, longitude: 110.2902)
/* 漓江坐标3 */
static let liRiver3 = CLLocationCoordinate2D(latitude: 25.27409, longitude: 110.29009)
/* 漓江坐标4 */
static let liRiver4 = CLLocationCoordinate2D(latitude: 25.27419, longitude: 110.29009)
/* 漓江坐标5 */
static let liRiver5 = CLLocationCoordinate2D(latitude: 25.27409, longitude: 110.29020)
}
- 详细代码
struct MapPracticesContent: View {
var body: some View {
Map() {
Annotation("漓江臻美🚀", coordinate: .liRiver1, anchor: .zero) {
Image(systemName: "paperplane.circle")
.foregroundStyle(Color.green)
.fontWeight(.semibold)
.padding(5)
.background(Color.black)
.clipShape(Circle())
}
Marker("漓江旁边", systemImage: "backpack", coordinate: .liRiver2)
Marker("标记 3", systemImage: "graduationcap.fill", coordinate: .liRiver3)
Marker("标记 4", systemImage: "graduationcap.fill", coordinate: .liRiver4)
Marker("标记 5", systemImage: "paperclip.circle", coordinate: .liRiver5)
MapCircle(center: .liRiver4, radius: CLLocationDistance(1))
.foregroundStyle(Color.blue.opacity(0.6))
MapPolygon(coordinates: [
CLLocationCoordinate2D.liRiver2,
CLLocationCoordinate2D.liRiver5,
CLLocationCoordinate2D.liRiver3])
.foregroundStyle(Color.orange.opacity(0.7))
}
}
}
我们简单介绍了下 MapContentBuilder, 现在我们熟悉下Map的其他几个参数:
Map(position: Binding<MapCameraPosition>,
bounds: MapCameraBounds?,
interactionModes: MapInteractionModes,
selection: Binding<MapFeature?>,
scope: Namespace.ID?,
content: ()->C)
postion:位置信息,通过MapCameraPosition结构体去进行初始化位置以及确定地图中的位置,我们可以通过传入状态值跟地图进行数据的双向绑定,实时更改地图中显示的相机窗口位置。
MapCameraPosition是一个结构主要是描述如何在地图中定位地图相机的位置。当您将MapCameraPosition作为绑定传递给地图时,地图会调整其相机以框定请求的内容,或与MapCameraPosition指定的相机完全匹配。如果一个人以移动地图的方式与地图交互,地图会将位置重置为指定positedByUser的值。 它具有很多属性方法,用的比较多的是.region以及.camera,这两方法都是返回MapCaremaPosition,后续将使用其一进行演示。
bounds: 设置最大距离以及最小距离,用于定义地图的相机视图边界(视角限制),可以限制用户在地图上浏览的区域,确保他们无法将地图平移或缩放到指定区域之外。
假设你有一个应用,需要限制用户只能在特定区域内浏览地图,例如某个城市或特定的地理范围。你可以通过设置
MapCameraBounds来实现这一点。
Map(
...
/*
center: 定义地图视角的中心点,在这个示例中设为旧金山。
span: 定义中心点的经度以及纬度位置的偏差范围(纬度差以及经度差)。
minimumDistance: 最小缩放距离(以米为单位)
maximumDistance: 最大的缩放距离(以米为单位)
主要这里创建时不要使用MKCoordinateSpanMake,因为已经被废弃,直接使用MKCoordinateSpan即可
*/
bounds: MapCameraBounds(
centerCoordinateBounds: MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.2)
),
minimumDistance: 100,
maximumDistance: 100),
...
)
interactionModes:跟地图的交互形式,MapInteractionModes类型定义了如平移、俯仰、旋转和缩放交互形式。默认情况下,它启用所有可用的交互类型。selection:绑定地图上被选择的符合MapFeature特征的元素。
MapFeature: 用于在地图上添加描述和操作地图上的可进行点击的特定元素(例如:标记、覆盖物、区域等),它是MapKit上的功能。
在 MapKit 中,你可以使用以下组件来添加和管理地图上的特性:
MKPointAnnotation: 用于在地图上添加标记。MKOverlay: 用于在地图上添加覆盖物(例如,圆形、矩形、线条等)。MKGeodesicPolyline: 用于绘制地理折线。MKCircle: 用于绘制圆形覆盖物。
关于
MapFeature更多细节后续讲解...其实目前还没研究明白,基础使用还可以!后续继续更新~
scope: 定义命名空间,主要作用就是我们可以创建命名空间绑定到地图组件上,后续 SwiftUI可以跟其进行关联从而达到控制地图操作的作用!
地图样式 MapStyle
指定地图的样式,当前遵循MapStyle结构类型,MapStyle有预先配置好的地图样式可以选择,如果没有特别要求,基本可以满足。
.standard: 标准样式.imagery: 基于图像的风格,如使用卫星图像的风格。.hybrid: 混合风格,例如使用一个地区的卫星图像,并在其上添加道路和路名信息层。
其次我们可以添加额外配置,控制是否渲染高度(2D, 3D切换)、地图强调风格,显示兴趣点和显示交通情况等。
Map(){
...
}
.mapStyle(
MapStyle.standard(
elevation: .flat,
emphasis: .muted,
pointsOfInterest: PointOfInterestCategories(arrayLiteral: .publicTransport),
showsTraffic: true))
地图交互
Map地图上的交互主要通过配置interactionModes进行设置,有下面几种类型:
.all: 允许所有的交互类型.pin: 允许用户平移到地图的不同区域.zoom: 允许用户放大或缩小地图位置.pitch: 允许用户设置地图的间距,从不同的角度查看地图.rotate: 允许用户对地图进行旋转操作
Map(
...
interactionModes: [.pitch, .pin],
...){}
地图控件
我们都知道在现有的地图应用中大都会有这些地图的配件,如指南针、当前用户位置、倾斜、比例尺和缩放等控件,在SwiftUI中都实现为SwiftUI视图形式,这就表示我们可以将他们放置的位置不一定要在Map结构里,但是我们需要通过定义一个地图范围命名空间,以将它们与它们控制的地图关联起来。
通过命名空间关联地图控件
import SwiftUI
import MapKit
extension CLLocationCoordinate2D {
static let liRiver1 = CLLocationCoordinate2D(latitude: 25.2740, longitude: 110.2901)
}
struct MapPracticesContent: View {
@Namespace var mapSpace
@State private var position: MapCameraPosition = MapCameraPosition.camera(MapCamera(centerCoordinate: .liRiver1, distance: 0.1))
let mapCameraBounds: MapCameraBounds = MapCameraBounds(centerCoordinateBounds: MKCoordinateRegion(center: .liRiver1, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.2)), minimumDistance: 100, maximumDistance: 100)
let mapInteractionModes: MapInteractionModes = .all
@State private var selection: MapFeature? = .none
var body: some View {
Map(position: $position, bounds: mapCameraBounds, interactionModes: mapInteractionModes, selection: $selection, scope: mapSpace) {
Marker(item: MKMapItem(placemark: MKPlacemark(coordinate: .liRiver1)))
}
.mapScope(mapSpace)
/* 指南针 */
MapCompass(scope: mapSpace)
}
}
其实还有一种标准的方式统一位置定义我们的地图空间,不需要借助命名空间的帮助:
Map(){}
.mapControls({
/* 指南针 */
MapCompass()
/* 仅适用于 WatchOS的指南针 */
#if os(watchOS)
MapLocationCompass()
#endif
#if os(macOS)
/* 控制地图是否显示高度,也就是 2D / 3D */
MapPitchSlider()
/* 允许用户调节缩放比例 */
MapZoomStepper()
#endif
MapPitchToggle()
/* 用于显示地图比例尺的一个控件。比例尺可以帮助用户了解地图上显示的距离,与实际世界中的距离的比例关系 */
MapScaleView(anchorEdge: .trailing)
/* 设置关联的地图位置为当前用户的位置: 右上角的箭头 */
MapUserLocationButton()
})
简单应用演示
后续更新...
欢迎你的阅读查看,希望对你有帮住,文档持续更新中!