对 @Published 数组的更改不会出现在 MapKit 中的 Map() 上

问题描述 投票:0回答:1

我正在尝试在我的应用程序中实现地图功能。我在使用 UIKit 和 SwiftUI 时遇到了 MapKit 问题。这是我的问题。我在名为 EventViewModel 的类中有一个名为 events 的 @Published 数组。我将此视图模型作为环境对象传递,因为我在许多视图中使用它。在一个视图中,我接收用户输入并将事件添加到数组中。这工作正常,我的事件以列表设计呈现。但是,我还在 Map() 中使用事件数组作为注释项,因为事件包含位置坐标。我的问题是当我添加新事件时,新注释不会在地图上呈现。但是,如果我在 @Published 事件数组中预加载了数据,则注释加载正常。

这是与 UIKit 交互的 SwiftUI

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)
    }
}


最后这是我在另一个视图中的createEvent()函数,它也使用了

@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)
        }
    }

起初我完全从 MapKit 的 SwiftUI 版本开始,但发现了同样的问题,即当事件附加到数组时注释不会更新。正如许多人所说,切换到 UIKit 可以提供更大的灵活性,因为当前版本的 MapKit 仍然相当有限。

swift swiftui uikit mapkit mapkitannotation
1个回答
0
投票

只需更改:

let eventViewModel: EventViewModel

let events: [Event]

然后实施:

func updateUIViewController(...

每当

events
发生变化时都会调用此函数。从地图中删除数组中不再存在的所有内容并添加新的内容。

我们在 SwiftUI 中不使用视图模型对象,

View
结构已经是视图模型了。

© www.soinside.com 2019 - 2024. All rights reserved.