译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
往 MKMapView
添加标记,是一种在位置上放置饰针的操作。在这个 app 中,我们希望可以点击这个标记获取更多信息,并且可以编辑。为了实现这个功能,我们需要用到一些 SwiftUI 的知识,一些 UIKit 的知识,还有一些 MapKit 的知识, 所有这些融合到一起,很有趣的挑战呢!
第一步是实现mapView(_:viewFor:)
方法,它在我们需要为地图上的大头针提供一个自定义视图时被调用。这个我们之前讨论过,不过现在要用一个更高级的解决方案,它会重用视图以便提升性能,并且还会添加一个按钮用于获取更多信息。MapKit 处理这个按钮点击的方式比较稀奇,但不难,只是咋一看比较奇怪。
言归正传,我们的主要目的是复用视图以提升性能。记住,创建视图的开销是昂贵的,所以最好能够尽可能少地创建,尽可能多地回收利用 —— 比如只改变文本视图的标签而不是每次都销毁然后重新创建文本视图。
MapKit 给了我们一个简单的 API ,用以处理视图重用:我们先选择一个字符串标识符,然后用它作为参数在地图视图上调用 dequeueReusableAnnotationView(withIdentifier:)
。如果有视图等着被回收,我们能够拿到它并且根据需求重新配置;如果没有,我们得到nil
,于是就必须自行创建视图。
如果我们确实得到 nil
,那说明我们得实例化一个新的 MKPinAnnotationView
,把它传给标记用作显示。此外,我们还需要设置一个叫 rightCalloutAccessoryView
的属性,它是在标记上放置一个显示更多信息按钮的地方。
这里不属于 SwiftUI 的地盘,意味着我们不能用 Button
视图。取而代之的是,我们需要用 UIKit 里的对应控件 —— UIButton
。对于 UIButton
,我可以花了几个小时教你如何使用这个控件,不过幸运的是我并不需要这么做:在这里只有一行代码,因为我们可以一个内建的按钮风格,叫 .detailDisclosure
—— 它看起来就像一个中间有一个 "i" 的圆。
如同 UIKit 和 MapKit 中所有的代理方法,下一个要讨论的方法的名字很长。所以最好在 Coordinator
类里输入 “viewfor” ,让 Xcode 的代码补全弹出来。正确的MKMapView
方法会弹出来,你只需要按下 return 让它输出完整的方法。
完善代码如下:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // this is our unique identifier for view reuse let identifier = "Placemark" // attempt to find a cell we can recycle var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) if annotationView == nil { // we didn't find one; make a new one annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier) // allow this to show pop up information annotationView?.canShowCallout = true // attach an information button to the view annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) } else { // we have a view to reuse, so give it the new annotation annotationView?.annotation = annotation } // whether it's a new view or a recycled one, send it back return annotationView }复制代码
只有这部分还无法工作,因为尽管我们已经设置canShowCallout
为 true ,没有标题的话 MapKit 不会为标记展示 callout 。目前我们还没有地方可以输入标题,所以我们先硬编码一个 —— 回到 ContentView.swift ,在创建 locations 数组的地方添加一行代码:
newLocation.title = "Example location"复制代码
运行 app ,你会发现我们可以通过点按 + 按钮来添加饰针了。当你点击饰针时,会弹出地点标题 —— 右边还有一个小小的 "i" 按钮。让这个按钮工作是这个工程中最有趣的部分之一。
首先我们先往 MapView
里添加两个属性,追踪我们是否要显示细节,以及实际选择的地点。它们将充当 MKMapView
和 SwiftUI 之间的桥梁,所以我们会把它们标记为 @Binding
。
添加下面两个属性:
@Binding var selectedPlace: MKPointAnnotation? @Binding var showingPlaceDetails: Bool复制代码
我个人喜好把所有的 @Binding
属性放在一起,这会影响到 Swift 如何创建它的逐一成员构造器。
添加了这些额外的属性也意味着 MapView_Previews
结构体也需要相应的改动,就像这样:
MapView(centerCoordinate: .constant(MKPointAnnotation.example.coordinate), selectedPlace: .constant(MKPointAnnotation.example), showingPlaceDetails: .constant(false), annotations: [MKPointAnnotation.example])复制代码
在 ContentView.swift 里我们要做的差不多,先添加一个 @State 属性到 ContentView
:
@State private var selectedPlace: MKPointAnnotation? @State private var showingPlaceDetails = false复制代码
然后更新 MapView
传入这些值:
MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations)复制代码
当 showingPlaceDetails
布尔值变为 true 时,我们想要显示一个 alert ,标题和副标题展示当前选中的地点,并且还有一个可供用户编辑地点的按钮。虽然现在还没实现编辑,但至少可以展示这个 alert ,并且和 MapKit 连起来。
添加 alert()
modifier 到 ContentView 的 ZStack 里:
.alert(isPresented: $showingPlaceDetails) { Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information."), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) { // edit this place }) }复制代码
最后,我们需要更新 MapView
,当我们点击标注的 "i" 按钮时,设置 selectedPlace
和 showingPlaceDetails
属性。这个功能是通过实现一个更长的方法来完成的。再一次,利用 Xcode 的代码补全。
这个方法,最重要的部分叫 calloutAccessoryControlTapped
,它是 callout 的按钮被点击时调用的,而点击时发生什么由我们决定。在这个实例中,我们首先需要确保我们确实有一个 MKAnnotationView
,然后用它设置父视图的 selectedPlace
属性,然后再把 showingPlaceDetails
设置为 true ,最终触发 ContentView
中的 alert —— 这是另一个链条,这一次是把地图的饰针 callout 的点击和我们的 alert 连在一起。
把下面的方法添加到 Coordinator
类:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { guard let placemark = view.annotation as? MKPointAnnotation else { return } parent.selectedPlace = placemark parent.showingPlaceDetails = true }复制代码
通过这一步的,我们的项目就完成了。现在运行 app —— 你可以放置饰针,点击它会显示更多信息,再点击 "i" 按钮会显示一个 alert ,齐活!
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~