问题描述
我正在创建我的第一个 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 任务可能会在不同的线程上返回)。