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

@Published数组更新后MapKit地图未显示新标注问题求助

MapKit标注不更新问题排查与解决

问题描述

在SwiftUI和UIKit混合环境下使用MapKit时,EventViewModel中定义的@Published数组events添加新Event后,地图标注无法自动更新,但列表视图能正常渲染新数据;如果数组中有预加载数据,标注则能正常显示。无论是纯SwiftUI版本的MapKit,还是用UIKit封装的MapKit,都存在相同问题。

相关代码

SwiftUI与UIKit交互的Map实现

import SwiftUI

struct MapView: View {
    @EnvironmentObject var eventViewModel: EventViewModel
    
    var body: some View {
        MapViewController(eventViewModel: eventViewModel)
            .ignoresSafeArea()
    }
}

struct MapViewController: UIViewControllerRepresentable {
    typealias UIViewControllerType = ViewController
    
    let eventViewModel: EventViewModel
    
    func makeUIViewController(context: Context) -> ViewController {
        let vc = ViewController()
        vc.eventViewModel = eventViewModel
        return vc
    }
    
    func updateUIViewController(_ uiViewController: ViewController, context: Context) {
       
    }
}

class ViewController: UIViewController {
    var locationManager: CLLocationManager?
    var eventViewModel: EventViewModel? {
        didSet {
            subscribeToEventChanges()
        }
    }

    private var cancellables: Set<AnyCancellable> = []

    private func subscribeToEventChanges() {
           eventViewModel?.$events
               .receive(on: DispatchQueue.main)
               .sink { [weak self] _ in
                   self?.updateMapAnnotations()
               }
               .store(in: &cancellables)
    }

    private func updateMapAnnotations() {
       // Clear existing annotations on the map
       mapView.removeAnnotations(mapView.annotations)

       // Add new annotations based on the updated eventViewModel.events
       addEventPins()
        
       print("Update Map Annotations") // Get's into this function when an event is added
    }
    
    lazy var mapView: MKMapView = {
        let map = MKMapView()
        map.showsUserLocation = true
        map.isRotateEnabled = false
        map.translatesAutoresizingMaskIntoConstraints = false
        return map
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        locationManager = CLLocationManager()
        locationManager?.delegate = self
        locationManager?.requestWhenInUseAuthorization()
        locationManager?.requestLocation()
        
        mapView.delegate = self
        
        createMap()
        addEventPins()
    }
    
    private func createMap() {
        view.addSubview(mapView)
        
        mapView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        mapView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
        mapView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        mapView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
    
    private func checkLocationAuthorization() {
        guard let locationManager = locationManager,
              let location = locationManager.location else { return }
        
        switch locationManager.authorizationStatus {
            case .authorizedAlways, .authorizedWhenInUse:
                let region = MKCoordinateRegion(
                    center: location.coordinate,
                    latitudinalMeters: 750,
                    longitudinalMeters: 750
                )
                mapView.setRegion(region, animated: true)
            case .notDetermined, .restricted:
                print("Location cannot be determined or is restricted")
            case .denied:
                print("Location services have been denied.")
            @unknown default:
                print("Unknown error occurred. Unable to get location.")
        }
    }
    
    private func addEventPins() {
        guard let eventViewModel = eventViewModel else { return }
        
        for event in eventViewModel.events {
            let annotation = MKPointAnnotation()
            annotation.coordinate = event.coordinate
            annotation.title = event.name
            annotation.subtitle = event.host
            
            let _ = LocationAnnotationView(annotation: annotation, reuseIdentifier: "eventAnnotation")
            
            mapView.addAnnotation(annotation)
            print(mapView.annotations) // Properly adds the annotation when an event is added
        }
    }
}

final class LocationAnnotationView: MKAnnotationView {
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
       super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

       centerOffset = CGPoint(x: -125, y: -125)
        
       canShowCallout = true
       setupUI(name: annotation?.title ?? "")
   }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI(name: String?) {
        backgroundColor = .clear
        
        let hostingController = UIHostingController(rootView: Pin(name: name ?? ""))
        hostingController.view.frame = CGRect(x: 0, y: 0, width: 250, height: 250)
       
        hostingController.view.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
        
        addSubview(hostingController.view)
        
        hostingController.view.backgroundColor = .clear
    }
}

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if annotation is MKUserLocation {
            return nil
        }

        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "locationAnnotation")
        
        if annotationView == nil {
            annotationView = LocationAnnotationView(annotation: annotation, reuseIdentifier: "locationAnnotation")
        } else {
            annotationView?.annotation = annotation
        }

        return annotationView
    }
}

extension ViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
    }
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        checkLocationAuthorization()
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}

创建Event的代码

@EnvironmentObject var eventViewModel: EventViewModel

private func createEvent() {
        var coordinates = CLLocationCoordinate2D()
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(self.location) {
            placemarks, error in
            let placemark = placemarks?.first
            let lat = placemark?.location?.coordinate.latitude
            let lon = placemark?.location?.coordinate.longitude
            
            // Format is Address, City, State Zipcode
            coordinates = CLLocationCoordinate2D(latitude: lon ?? CLLocationDegrees(), longitude: lat ?? CLLocationDegrees())
            print(coordinates)
            let event = Event(name: self.eventName, host: self.hosts.first ?? "No Host", date: self.date, coordinate: coordinates)
            
            let date = Calendar.current.startOfDay(for: event.date)
            
            eventViewModel.groupedEvents[date, default: []].append(event)
            eventViewModel.events.append(event)
            
            print(eventViewModel.events)
        }
    }

问题根源与修复方案

1. 坐标赋值颠倒(核心问题)

在createEvent方法中,构造CLLocationCoordinate2D时错误地将经度赋值给了纬度,纬度赋值给了经度:

// 错误代码
coordinates = CLLocationCoordinate2D(latitude: lon ?? CLLocationDegrees(), longitude: lat ?? CLLocationDegrees())

这会导致标注被添加到无效的地理区域(比如接近0,0的大西洋区域),看起来像是没显示在地图上。修复后:

// 正确代码
coordinates = CLLocationCoordinate2D(latitude: lat ?? 0.0, longitude: lon ?? 0.0)

2. 订阅重复问题

ViewController的eventViewModel属性每次赋值时,都会创建新的订阅但未清理旧订阅,导致多次执行updateMapAnnotations。修改subscribeToEventChanges方法,先清空旧订阅:

private func subscribeToEventChanges() {
    cancellables.removeAll() // 清理旧订阅,避免重复触发
    eventViewModel?.$events
        .receive(on: DispatchQueue.main)
        .sink { [weak self] _ in
            self?.updateMapAnnotations()
        }
        .store(in: &cancellables)
}

3. 标注更新逻辑优化

updateMapAnnotations中当前会移除所有标注(包括用户位置),可以优化为只移除非用户位置的标注,避免不必要的操作:

private func updateMapAnnotations() {
    // 过滤并移除非用户位置的旧标注
    let nonUserAnnotations = mapView.annotations.filter { !($0 is MKUserLocation) }
    mapView.removeAnnotations(nonUserAnnotations)
    
    addEventPins()
}

4. 纯SwiftUI MapKit补充

如果回到纯SwiftUI版本的MapKit,确保:

  • Event结构体遵循Identifiable协议
  • 标注视图正确绑定event.coordinate
    示例代码:
struct Event: Identifiable {
    let id = UUID()
    var name: String
    var host: String
    var date: Date
    var coordinate: CLLocationCoordinate2D
}

// 在SwiftUI视图中
Map(annotationItems: eventViewModel.events) { event in
    MapAnnotation(coordinate: event.coordinate) {
        Pin(name: event.name)
    }
}

内容的提问来源于stack exchange,提问作者Dmadjar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:22:33