SwiftUI:更高级的 MKMapView
该项目将以地图视图为基础,要求用户将要访问的地方添加到地图中。为了完成这项工作,我们不能只是在SwiftUI中嵌入一个简单的MKMapView
,希望做到最好:我们需要跟踪中心坐标,用户是否在查看地点详细信息,他们拥有哪些标注等等。
因此,我们将从具有协调器的基本MKMapView
包装器开始,然后在其上快速添加一些附加功能,以使其变得更加有用。
创建一个名为“ MapView”的新SwiftUI视图,为 MapKit 添加导入,然后为其提供以下代码:
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
}
那里没有什么特别的,所以让我们跟踪地图的中心坐标来立即进行更改。正如我们之前所看的,这意味着在我们的协调器中实现mapViewDidChangeVisibleRegion()
方法,但是这次我们将把数据传递给 MapView
结构,以便可以使用@Binding
将值存储在其他位置。因此,协调器将从 MapKit 接收值并将其传递给MapView
,该MapView
将值放在@Binding
属性中,这意味着它实际上存储在其他位置——我们将MKMapView
连接到了任何嵌入了地图的 SwiftUI 视图对象。
首先将此属性添加到MapView
:
@Binding var centerCoordinate: CLLocationCoordinate2D
这将立即破坏MapView_Previews
结构体,因为它需要提供绑定。该预览并不是真正有用的,因为MKMapView
在模拟器之外无法运行,因此,如果您删除了它,我也不会怪您。但是,如果您真的想使其工作,则应向MKPointAnnotation
添加一些示例数据,以便于参考:
extension MKPointAnnotation {
static var example: MKPointAnnotation {
let annotation = MKPointAnnotation()
annotation.title = "London"
annotation.subtitle = "Home to the 2012 Summer Olympics."
annotation.coordinate = CLLocationCoordinate2D(latitude: 51.5, longitude: -0.13)
return annotation
}
}
将其放置在适当的位置即可轻松修复MapView_Previews
,因为我们可以使用该示例注释:
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(centerCoordinate: .constant(MKPointAnnotation.example.coordinate))
}
}
我们将在短时间内添加更多内容,但是首先我想将其放入ContentView
。在这个应用程序中,用户将要向地图上添加他们想要访问的地点,我们将通过全屏MapView
和顶部的半透明圆来表示中心点来表示这些地点。尽管此视图将具有绑定来跟踪中心坐标,但是我们不需要使用该绑定来放置圆——简单的ZStack
可以确保圆始终位于地图的中心。
首先,添加一条额外的导入行,以便我们可以访问MapKit的数据类型:
import MapKit
其次,在ContentView
内部添加一个属性,该属性将存储地图的当前中心坐标。稍后,我们将使用它添加一个地标:
@State private var centerCoordinate = CLLocationCoordinate2D()
现在我们可以填写body
属性
ZStack {
MapView(centerCoordinate: $centerCoordinate)
.edgesIgnoringSafeArea(.all)
Circle()
.fill(Color.blue)
.opacity(0.3)
.frame(width: 32, height: 32)
}
如果您现在运行该应用程序,您会看到可以自由移动地图,但是始终会有一个蓝色圆圈显示中心位置。
尽管我们的蓝点将始终固定在地图的中心,但是我们仍然希望ContentView
在地图移动时更新其centerCoordinate
属性。我们已经将其连接到MapView
,但是仍然需要在地图视图的协调器中实现mapViewDidChangeVisibleRegion()
方法,以启动整个链。
因此,现在将此方法添加到MapView
的Coordinator
类中:
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.centerCoordinate = mapView.centerCoordinate
}
所有这些工作本身并不十分有趣,因此下一步是在右下角添加一个按钮,使我们可以在地图上添加地标。我们已经在ZStack
中,因此最简单的对齐此按钮的方法是将其放置在VStack
和HStack
中,每次放置之前都带有间隔(spacers)。这两个间隔物最终都占据了剩下的全部垂直和水平空间,从而使结尾处的所有内容都舒适地位于右下角。
我们将很快为该按钮添加一些功能,但首先让它安装到位并添加一些基本样式以使其看起来不错。
请将此VStack
添加到Circle
下方:
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
// 创建新的位置
}, label: {
Image(systemName: "plus")
})
.padding()
.background(Color.black.opacity(0.75))
.foregroundColor(.white)
.font(.title)
.clipShape(Circle())
.padding(.trailing)
}
}
PS: 此处Button
创建的闭包使用了Swift 5 修改后的尾随闭包样式,如果拷贝编译不了,请自己手动敲一下就好
请注意,我是如何在其中两次添加padding()
修饰符的——一次是在添加背景色之前确保按钮更大,然后是第二次是设置整个按钮的对边缘增加一些距离。
有趣的是我们如何在地图上放置图钉。我们已将地图的中心坐标绑定到地图视图中的属性,但是现在我们需要以其他方式发送数据——我们需要在ContentView
中创建位置数组,然后将其发送到MKMapView
进行显示。
解决此问题的最佳方法是将问题分解为几个更小,更简单的部分。第一部分很明显:我们在ContentView
中需要一个位置数组,用于存储用户要访问的所有位置。
因此,首先将此属性添加到ContentView
:
@State private var locations = [MKPointAnnotation]()
接下来,我们想在每当点击 + 按钮时添加一个位置。我们还不会添加标题和副标题,因此,现在这就像使用centerCoordinate
的当前值创建MKPointAnnotation
一样简单。
替换// 创建新的位置
注释:
let newLocation = MKPointAnnotation()
newLocation.coordinate = self.centerCoordinate
self.locations.append(newLocation)
现在是具有挑战性的部分:我们如何将其与地图视图同步?请记住,我们甚至不希望ContentView
知道正在使用 MapKit,而是希望将所有功能隔离在MapView
中,以便我们的SwiftUI代码保持干净。
这是updateUIView()
出现的地方:当发送到UIViewRepresentable
结构体的任何值发生更改时,SwiftUI都会自动调用它。然后,此方法负责将视图及其协调器与父视图中的最新配置同步。
在我们的例子中,我们将centerCoordinate
绑定发送到MapView
中,这意味着每当用户移动地图时,值都会更改,这又意味着始终在调用updateUIView()
。由于updateUIView()
为空,所以一直在悄悄发生这种情况,但是如果您在其中添加一个简单的print()
调用,就会发现它栩栩如生:
func updateUIView(_ view: MKMapView, context: Context) {
print("Updating")
}
现在,在四处移动地图时,您会一次又一次看到“Updating”打印。
无论如何,所有这些都很重要,因为我们还可以将刚才创建的locations
数组传递到MapView
中,并使用该数组为我们插入标注。
因此,首先将此新属性添加到MapView
中,以保存我们将传递给它的所有位置:
var annotations: [MKPointAnnotation]
其次,我们需要更新 MapView_Previews
,以便它发送我们的示例标注,尽管如果您已经删除了预览,我也不会怪您,因为这实际上并没有用!无论如何,如果您仍然拥有它,则将其调整为:
MapView(centerCoordinate: .constant(MKPointAnnotation.example.coordinate), annotations: [MKPointAnnotation.example])
第三,我们需要在MapView
中实现updateUIView()
,以便将当前标注与最新标注进行比较,如果它们不相同,则将其替换。现在,我们可以比较标注中的每个项目以查看它们是否相同,但是没有任何意义——我们不能同时添加和删除项目,因此我们要做的就是检查这两个项目是否相同数组包含相同数量的项目,如果它们不同,则删除所有现有的标注并再次添加它们。
将此替换为当前的updateUIView()
方法:
func updateUIView(_ view: MKMapView, context: Context) {
if annotations.count != view.annotations.count {
view.removeAnnotations(view.annotations)
view.addAnnotations(annotations)
}
}
最后,更新ContentView
,使其发送locations
数组以将其转换为标注:
MapView(centerCoordinate: $centerCoordinate, annotations: locations)
到目前为止,地图工作已经足够了,因此请继续运行您的应用——您应该可以根据需要进行任意移动,然后按+按钮添加图钉。
您可能会注意到的一件事是,当iOS针紧密放置时,它们如何自动合并。例如,如果将一些图钉放在一公里的区域中然后缩小,则iOS将隐藏其中的一些图钉,以避免使地图难以阅读。
- Golang中Interface类型详解
- Go语言的网络编程简介
- 一条关于swap争用的报警邮件分析(二)(r8笔记第4天)
- Golang泛型编程初体验
- 厚土Go学习笔记 | 14. switch 的条件写的有点灵活,不过风格还是go的一贯风格
- Go语言·我的性能我做主
- 47. 访问MySql数据库实现增删改查 | 厚土Go学习笔记
- system表空间不足的问题分析(二) (r8笔记第5天)
- golang基于redis lua封装的优先级去重队列
- python基础知识——内置数据结构(元组)
- python基础知识——控制语句
- python基础知识——基本语法
- 11g主库归档自动删除的小问题分析 (r8笔记第1天)
- JavaWeb02-CSS,JS(Java真正的全栈开发)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python钉钉报警及Zabbix集成钉钉报警
- 无锁队列的实现
- Go 单元测试和性能测试
- 01 . etcd简介原理,应用场景及部署,简单使用
- GO 单例模式
- 关于本博客皮肤样式配置
- 03 . Go开发一个日志平台之Elasticsearch使用及kafka消费消息发送到Elasticsearch
- GO 匿名函数和闭包
- Nginx升级加固SSL/TLS协议信息泄露漏洞(CVE-2016-2183)和HTTP服务器的缺省banner漏洞
- GO中间件(Middleware )
- TomcatAJP文件包含漏洞及线上修复漏洞
- golang new和make的区别
- Magicodes.IE之导入导出筛选器
- 界面酷炫,功能强大!这款 Linux 性能实时监控工具超好用!老斯机搞它!
- httprunner学习28-yaml文件 参数化读取 csv 文件字符串转 int