来自 GeoJSON URL 端点的数据未使用 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 { $0 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)")
    }
}

这是模型类 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 类: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 任务可能会在不同的线程上返回)。