使用 MKAnnotations 来自 GeoJSON URL 端点的数据未出现在地图上

Data from GeoJSON URL end point not appearing on Map using MKAnnotations

我正在创建我的第一个 IOS 应用程序,我不是开发人员,而且我真的很喜欢地图注释。

我正在尝试从 GeoJSON URL 端点获取火灾数据,并使用 URLSession 和自定义 MKAnnotationView 以及自定义火针将火灾显示为地图上的注释。

问题是来自 URL 端点的 GeoJSON Fire 数据的注释没有出现在地图上,尽管 URL 会话正在返回数据。但是,如果我手动创建单个 Fire 注释,它会使用自定义图钉正确显示在地图上。

任何帮助将不胜感激,我花了几天时间试图解决这个问题:(

这是ViewController.Swift文件

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {

    @IBOutlet weak var mapView: MKMapView!
    var locationManager:CLLocationManager!
    var lat = Double()
    var lon = Double()
    var fires: [Fire] = []
    
    override func viewDidLoad() {

        super.viewDidLoad()
     
        mapView.delegate = self
        mapView.register(FireMarkerView.self,forAnnotationViewWithReuseIdentifier:
            MKMapViewDefaultAnnotationViewReuseIdentifier)
          
        if let url = URL(string: "https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Active_Fires/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson") {
              
            URLSession.shared.dataTask(with: url) {data, response, error in
                    if let data = data {
                       do {
                           let features = try MKGeoJSONDecoder().decode(data)
                               .compactMap { [=11=] as? MKGeoJSONFeature }
                             let validWorks = features.compactMap(Fire.init)
                        self.fires.append(contentsOf: validWorks)
                        print([self.fires])
                        
                                 }
                       catch let error {
                                    print(error)
                                   
                       }
                     }
                 }.resume()
           }
        
        
//This code works an annotation appears correctly on map
        /* let fire = Fire(
        title: "Ford Fire",
        incidentShortDescription: "Hwy 35",
        incidentTypeCategory: "WF",
        coordinate: CLLocationCoordinate2D(latitude: 37.7993, longitude: -122.1947))
       
        mapView.addAnnotation(fire)*/
        
        mapView.addAnnotations(fires)
    }
        
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        determineMyCurrentLocation()
    }
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {

    switch manager.authorizationStatus {
        case .authorizedAlways , .authorizedWhenInUse:
            mapView.showsUserLocation = true
            followUserLocation()
            locationManager.startUpdatingLocation()
            break
        case .notDetermined , .denied , .restricted:
            locationManager.requestWhenInUseAuthorization()
            break
        default:
            break
    }
    
    switch manager.accuracyAuthorization {
        case .fullAccuracy:
            break
        case .reducedAccuracy:
            break
        default:
            break
    }
}
func followUserLocation() {
    if let location = locationManager.location?.coordinate {
        let region = MKCoordinateRegion.init(center: location, latitudinalMeters: 4000, longitudinalMeters: 4000)
        mapView.setRegion(region, animated: true)
    }
}

    func determineMyCurrentLocation() {
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestAlwaysAuthorization()
        
        if CLLocationManager.locationServicesEnabled() {
            locationManager.startUpdatingLocation()
            //locationManager.startUpdatingHeading()
        }
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        
        let userLocation = locations.first! as CLLocation
        lat = userLocation.coordinate.latitude
        lon = userLocation.coordinate.longitude
        
        let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: 400000, longitudinalMeters: 400000)
        self.mapView.setRegion(region, animated: true)

        // Call stopUpdatingLocation() to stop listening for location updates,
        // other wise this function will be called every time when user location changes.
        // Need a solution for this.
                manager.stopUpdatingLocation()
        
        print("user latitude = \(userLocation.coordinate.latitude)")
        print("user longitude = \(userLocation.coordinate.longitude)")
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
    {
        print("Error \(error)")
    }
}

这是模型Class,Fire.swift

import Foundation
import MapKit

class Fire: NSObject, MKAnnotation {
  let title: String?
  let incidentShortDescription: String?
  let incidentTypeCategory: String?
  let coordinate: CLLocationCoordinate2D

  init(
    title: String?,
    incidentShortDescription: String?,
    incidentTypeCategory: String?,
    coordinate: CLLocationCoordinate2D
  ) {
    self.title = title
    self.incidentShortDescription = incidentShortDescription
    self.incidentTypeCategory = incidentTypeCategory
    self.coordinate = coordinate

    super.init()
  }




init?(feature: MKGeoJSONFeature) {
  // 1
  guard
    let point = feature.geometry.first as? MKPointAnnotation,
    let propertiesData = feature.properties,
    let json = try? JSONSerialization.jsonObject(with: propertiesData),
    let properties = json as? [String: Any]
    else {
      return nil
  }

  // 3
    
  title = properties ["IncidentName"] as? String
  incidentShortDescription = properties["IncidentShortDescription"] as? String
  incidentTypeCategory = properties["IncidentTypeCategory"] as? String
  coordinate = point.coordinate
  super.init()
}
    var subtitle: String? {
        return (incidentTypeCategory)
    }
    
    
    var image: UIImage {
      guard let name = incidentTypeCategory else {
        return #imageLiteral(resourceName: "RedFlame")
      }

      switch name {
      case "RX":
        return #imageLiteral(resourceName: "YellowFlame")
      default:
        return #imageLiteral(resourceName: "RedFlame")
      }
    }

这里是自定义 MKAnnotation Class:FileMarkerView.swift

import Foundation
import MapKit

class FireMarkerView: MKAnnotationView {
  override var annotation: MKAnnotation? {
    willSet {
      guard let fire = newValue as? Fire else {
        return
      }

      canShowCallout = true
      calloutOffset = CGPoint(x: -5, y: 5)
      rightCalloutAccessoryView = UIButton(type: .detailDisclosure)

      image = fire.image
    }
  }
}

URLSession.shared.dataTask 是一个 异步 任务,这意味着它会在未来某个不确定的时间调用其回调函数。在其回调({ } 之外执行的代码将在数据任务实际完成之前被调用。现在,您正在该回调之外设置注释。

为了解决这个问题,你需要在那个回调函数的里面设置注解。所以,如果你有 print([self.fires]),你可以这样做:

DispatchQueue.main.async { 
  self.mapView.addAnnotations(self.fires)
} 

DispatchQueue.main.async 是为了确保在主线程上调用对 UI 的更新(URL 任务可能 return 在不同的线程上) .