@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
相关产品推荐
相关产品推荐

