You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 06:27:55