在MKMapView中点击标注跳转至ItemView的实现方法
问题
已完成从数据库拉取数据到MKMapView,并调用Apple服务将城市州转换为经纬度的功能,运行正常。现在需要实现:点击MKMapView中的标注时,跳转到ItemView并传入对应的ToDoItem。
相关代码如下:
自定义Annotation类
class ToDoLocationAnnotation: NSObject, MKAnnotation, Identifiable { let name: String? let coordinate: CLLocationCoordinate2D let toDoItem: ToDoItem init(toDoItem: ToDoItem, name: String?, coordinate: CLLocationCoordinate2D) { self.name = name self.toDoItem = toDoItem self.coordinate = coordinate } }
UIViewRepresentable的MapView
struct MapView: UIViewRepresentable { var items: FetchedResults<ToDoItem> init(items: FetchedResults<ToDoItem>) { self.items = items } func getCoordinateFrom(location: String, completion: @escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) { CLGeocoder().geocodeAddressString(location) { completion($0?.first?.location?.coordinate, $1) } } func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView(frame: .zero) mapView.delegate = context.coordinator return mapView } func updateUIView(_ view: MKMapView, context: Context) { let coordinate = CLLocationCoordinate2D( latitude: 39.299236, longitude: -76.609383) let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0) let region = MKCoordinateRegion(center: coordinate, span: span) view.setRegion(region, animated: true) items.forEach({ item in if item.completed == false { getCoordinateFrom(location: item.location!) { coordinate, error in guard let coordinate = coordinate, error == nil else { return } view.addAnnotation(ToDoLocationAnnotation(toDoItem: item, name: item.location!, coordinate: coordinate)) } } }) } }
Coordinator代理类
class Coordinator: NSObject, MKMapViewDelegate { var parent: MapView init(_ parent: MapView) { self.parent = parent } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { let view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: nil) view.canShowCallout = true return view } }
注:使用SwiftUI原生Map()组件时,可通过NavigationLink直接实现跳转,但当前使用的是UIViewRepresentable封装的MKMapView,不清楚具体实现方式。
实现步骤
因为是用UIViewRepresentable封装的MKMapView,无法直接使用SwiftUI的NavigationLink,需要通过绑定选中的Annotation + NavigationLink结合的方式实现跳转,具体步骤如下:
1. 给MapView添加选中Annotation的绑定属性
修改MapView结构体,添加@Binding属性记录用户选中的ToDoItem,用于触发跳转:
struct MapView: UIViewRepresentable { var items: FetchedResults<ToDoItem> // 添加绑定属性,记录选中的待办项 @Binding var selectedToDoItem: ToDoItem? init(items: FetchedResults<ToDoItem>, selectedToDoItem: Binding<ToDoItem?>) { self.items = items self._selectedToDoItem = selectedToDoItem } // 其余原有代码不变... }
2. 在Coordinator中实现标注点击代理方法
在Coordinator类中添加mapView(_:didSelect:)方法,点击标注时将对应的ToDoItem赋值给绑定属性:
class Coordinator: NSObject, MKMapViewDelegate { var parent: MapView init(_ parent: MapView) { self.parent = parent } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { let view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: nil) view.canShowCallout = true return view } // 添加标注点击代理方法 func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) { // 转换为自定义Annotation,取出对应的ToDoItem guard let todoAnnotation = annotation as? ToDoLocationAnnotation else { return } parent.selectedToDoItem = todoAnnotation.toDoItem } }
3. 在父视图中添加NavigationLink触发跳转
在包含MapView的父SwiftUI视图中,添加隐藏的NavigationLink,通过selectedToDoItem的变化触发跳转:
struct MapContainerView: View { @FetchRequest(sortDescriptors: []) var items: FetchedResults<ToDoItem> // 定义选中项的状态 @State private var selectedToDoItem: ToDoItem? var body: some View { NavigationStack { // 适配iOS 16+,iOS 15及以下用NavigationView MapView(items: items, selectedToDoItem: $selectedToDoItem) // 隐藏的NavigationLink,选中项有值时自动跳转 NavigationLink( destination: ToDoItemView(item: selectedToDoItem!), isActive: Binding( get: { selectedToDoItem != nil }, set: { if !$0 { selectedToDoItem = nil } } ), label: { EmptyView() } ) .hidden() } } }
补充优化:解决重复添加Annotation问题
当前updateUIView方法每次刷新都会重复请求地理编码并添加Annotation,可在方法开头清除旧的自定义Annotation:
func updateUIView(_ view: MKMapView, context: Context) { // 清除之前添加的自定义Annotation view.removeAnnotations(view.annotations.filter { $0 is ToDoLocationAnnotation }) // 原有地图区域设置代码... // 重新添加Annotation items.forEach({ item in if item.completed == false { getCoordinateFrom(location: item.location!) { coordinate, error in guard let coordinate = coordinate, error == nil else { return } view.addAnnotation(ToDoLocationAnnotation(toDoItem: item, name: item.location!, coordinate: coordinate)) } } }) }
内容的提问来源于stack exchange,提问作者KingFish
相关产品推荐
相关产品推荐

