2017-12-28 5 views
0

Ich habe ein Array von Orten, die ich auf einem MapView anzeige. Ich habe eine MKAnnotationView erstellt, um eine rechte Schaltfläche anzuzeigen. Ich zeige dann eine detailView an und übergebe Daten durch das Segment, aber es zeigt den falschen Platz an. Ich glaube, es gibt ein Problem mit meinen ausgewähltenAnnotationen. Der Benutzer kann nur jeweils eine Anmerkung auswählen.MKAnnotation View Segue

Gesamte Klasse

import UIKit 
import MapKit 

class MapViewController: UIViewController, PlacesModelDelegate, CLLocationManagerDelegate { 

    @IBOutlet weak var mapView: MKMapView! 

    var places = [Place]() 
    var model:PlacesModel? 
    var locationManager:CLLocationManager? 
    var lastKnownLocation:CLLocation? 

    var selectedAnnotation: Place? 

    // MARK: - Lifecycle 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     mapView.delegate = self 

     // Set map properties 
     mapView.showsUserLocation = true 

     // Instantiate location manager 
     locationManager = CLLocationManager() 
     locationManager?.delegate = self 

     // Instantiate places model if it is nil 
     if model == nil { 
      model = PlacesModel() 
      model?.delegate = self 
     } 

     // Call get places 
     model?.getPlaces() 

    } 

    override func didReceiveMemoryWarning() { 
     super.didReceiveMemoryWarning() 
     // Dispose of any resources that can be recreated. 
    } 

    // MARK: - Functions 

    func plotPins() { 

     var pinsArray = [MKPointAnnotation]() 

     // Go through the array of places and plot them 
     for p in places { 

      // Create a pin 
      let pin = MKPointAnnotation() 

      // Set its properties 
      pin.coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(p.lat), longitude: CLLocationDegrees(p.long)) 
      pin.title = p.name 
      pin.subtitle = p.getTypeName() 

      // Add it to the map 
      mapView.addAnnotation(pin) 

      // Store the pin in the pinsArray 
      pinsArray.append(pin) 
     } 

     // Center the map 
     mapView.showAnnotations(pinsArray, animated: true) 
    } 

    func displaySettingsPopup() { 

     // Create alert controller 
     let alertController = UIAlertController(title: "Couldn't find your location", 
               message: "Location Services is turned off on your device or the GuideBookApp doesn't have permission to find your location. Please check your Privacy settings to continue.", 
               preferredStyle: .alert) 

     // Create settings button action 
     let settingsAction = UIAlertAction(title: "Settings", style: .default) { (alertAction) in 

      if let appSettings = URL(string: UIApplicationOpenSettingsURLString) { 

       UIApplication.shared.open(appSettings, options: [:], completionHandler: nil) 

      } 
     } 
     alertController.addAction(settingsAction) 

     // Create cancel button action 
     let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 
     alertController.addAction(cancelAction) 

     // Show the alert 
     present(alertController, animated: true, completion: nil) 

    } 

    // MARK: - Button Handlers 

    @IBAction func locationButtonTapped(_ sender: UIButton) { 

     // Check if location services are enabled 
     if CLLocationManager.locationServicesEnabled() { 

      // They're enabled, now check the authorization status 
      let status = CLLocationManager.authorizationStatus() 

      if status == .authorizedAlways || status == .authorizedWhenInUse { 

       // Has permission 
       locationManager?.startUpdatingLocation() 

       // Center the map on last location 
       if let actualLocation = lastKnownLocation { 
        mapView.setCenter(actualLocation.coordinate, animated: true) 
       } 
      } 
      else if status == .denied || status == .restricted { 

       // Doesn't have permission 
       // Tell user to check settings 
       displaySettingsPopup() 
      } 
      else if status == .notDetermined { 

       // Ask the user for permission 
       locationManager?.requestWhenInUseAuthorization() 
      } 

     } 
     else { 
      // Locations services are turned off 
      // Tell user to check settings 
      displaySettingsPopup() 
     } 

    } 

    // MARK: - PlacesModelDelegate Methods 

    func placesModel(places: [Place]) { 

     // Set places property 
     self.places = places 

     // Plot the pins 
     plotPins() 
    } 

    // MARK: - CLLocationManagerDelegate Methods 

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 

     let location = locations.last 

     if let actualLocation = location { 

      // Create a pin 
      let pin = MKPointAnnotation() 
      pin.coordinate = CLLocationCoordinate2D(latitude: actualLocation.coordinate.latitude, longitude: actualLocation.coordinate.longitude) 

      // Center the map, only if it's the first time locating the user 
      if lastKnownLocation == nil { 
       mapView.setCenter(actualLocation.coordinate, animated: true) 
      } 

      // Save the pin 
      lastKnownLocation = actualLocation 
     } 

    } 

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 

     // See what the user has answered 
     if status == .denied { 

      // Tell user that this app doesn't have permission. They can change it in their settings 
      displaySettingsPopup() 
     } 
     else if status == .authorizedWhenInUse || status == .authorizedAlways { 

      // Permission granted 
      locationManager?.startUpdatingLocation() 
     } 

    } 

} 

extension MapViewController: MKMapViewDelegate { 


    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 
     let identifier = "marker" 
     var view: MKMarkerAnnotationView 

     view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier) 
     view.canShowCallout = true 
     view.calloutOffset = CGPoint(x: -5, y: 5) 
     view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) 

     return view 
    } 

    func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { 

     performSegue(withIdentifier: "mapSegue", sender: view) 

    } 
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 


     let selectedRow = mapView.selectedAnnotations.endIndex 

     let selectedPlace = places[selectedRow] 

     let detailModel = DetailModel() 
     detailModel.place = selectedPlace 

     let detailVC = segue.destination as! VenueDetailViewController 
     detailVC.model = detailModel 

    } 
} 

Orte Modell

import UIKit 

protocol PlacesModelDelegate { 

    func placesModel(places:[Place]) 

} 

class PlacesModel: NSObject, FirebaseManagerDelegate { 

    // Properties 
    var delegate:PlacesModelDelegate? 
    var firManager:FirebaseManager? 

    func getPlaces() { 

     // Get places from FirebaseManager 
     if firManager == nil { 
      firManager = FirebaseManager() 
      firManager!.delegate = self 
     } 

     // Tell firebase manager to fetch places 
     firManager!.getPlacesFromDatabase() 
    } 

    func checkDataVersion() { 

     // Get version from FirebaseManager 
     if firManager == nil { 
      firManager = FirebaseManager() 
      firManager!.delegate = self 
     } 

     firManager!.getVersionFromDatabase() 
    } 

    // MARK: - FirebaseManager Delegate Methods 

    func firebaseManager(places: [Place]) { 

     // Notify the delegate 
     if let actualDelegate = delegate { 
      actualDelegate.placesModel(places: places) 
     } 
    } 

} 

FirebaseManager

import UIKit 
import Firebase 

@objc protocol FirebaseManagerDelegate { 

    @objc optional func firebaseManager(places:[Place]) 
    @objc optional func firebaseManager(metaDataFor place:Place) 
    @objc optional func firebaseManager(imageName:String, imageData:Data) 

} 

class FirebaseManager: NSObject { 

    // MARK: - Properties 

    var ref: FIRDatabaseReference! 
    var delegate:FirebaseManagerDelegate? 

    // MARK: - Initializers 

    override init() { 

     // Initialize the database reference 
     ref = FIRDatabase.database().reference() 

     super.init() 
    } 


    // MARK: - Places Functions 

    func getPlacesFromDatabase() { 

     // Create an array to store all the places 
     var allPlaces = [Place]() 

     // Before we retrieve from Firebase, check cachemanager 
     if let cachedPlacesDict = CacheManager.getPlacesFromCache() { 

      // We have data in cache, parse that instead 
      // Call function to parse places dictionary 

      allPlaces = parsePlacesFrom(placesDict: cachedPlacesDict) 

      // Now return the places array 
      // Dispatch this code to be done on the main thread 
      DispatchQueue.main.async { 

       // Notify the delegate 
       if let actualDelegate = self.delegate { 
        actualDelegate.firebaseManager?(places: allPlaces) 
       } 
      } // End DispatchQueue 

      return 
     } 


     // Retrieve the list of Places from the database 
     ref.child("places").observeSingleEvent(of: .value, with: { (snapshot) in 



      let placesDict = snapshot.value as? NSDictionary 

      // See if data is actually present 
      if let actualPlacesDict = placesDict { 

       // We actually have a places dictionary 

       // Before working with the data, save it into cache 
       CacheManager.putPlacesIntoCache(data: actualPlacesDict) 

       // Call function to parse places dictionary 
       allPlaces = self.parsePlacesFrom(placesDict: actualPlacesDict) 

       // Now return the places array 
       // Dispatch this code to be done on the main thread 
       DispatchQueue.main.async { 

        // Notify the delegate 
        if let actualDelegate = self.delegate { 
         actualDelegate.firebaseManager?(places: allPlaces) 
        } 
       } // End DispatchQueue 
      } 

     }) // End observeSingleEvent 

    } // End getForYouFromDatabase 



    // MARK: - Meta Functions 

    func getMetaFromDatabase(place:Place) { 

     // Before fetching from firebase, check cache 
     if let cachedMetaDict = CacheManager.getMetaFromCache(placeId: place.id) { 

      // Parse the meta data 
      parseMetaFrom(metaDict: cachedMetaDict, place: place) 

      // Notify the delegate the the meta data has been fetched 
      // Dispatch this code to be done on the main thread 
      DispatchQueue.main.async { 

       // Notify the delegate 
       if let actualDelegate = self.delegate { 
        actualDelegate.firebaseManager?(metaDataFor: place) 
       } 
      } // End DispatchQueue 

      return 
     } 

     ref.child("meta").child(place.id).observe(.value, with: { (snapshot) in 

      // Get the dictionary from the snapshot 
      if let metaDict = snapshot.value as? NSDictionary { 

       // Save data into cache 
       CacheManager.putMetaIntoCache(data: metaDict, placeId: place.id) 

       // Parse firebase results 
       self.parseMetaFrom(metaDict: metaDict, place: place) 

       // Notify the delegate the the meta data has been fetched 
       // Dispatch this code to be done on the main thread 
       DispatchQueue.main.async { 

        // Notify the delegate 
        if let actualDelegate = self.delegate { 
         actualDelegate.firebaseManager?(metaDataFor: place) 
        } 
       } // End DispatchQueue 

      } 

     }) // End observeSingleEvent 

    } // End getMetaFromDatabase 

    func getImageFromDatabase(imageName:String) { 

     // Get the image 

     // Check cache first 
     if let imageData = CacheManager.getImageFromCache(imageName: imageName) { 

      // Notify the delegate on the main thread 
      DispatchQueue.main.async { 

       // Notify the delegate 
       if let actualDelegate = self.delegate { 
        actualDelegate.firebaseManager?(imageName: imageName, imageData: imageData) 
       } 
      } // End DispatchQueue 

      return 
     } 

     // Create the storage and file path references 
     let storage = FIRStorage.storage() 
     let imagePathReference = storage.reference(withPath: imageName) 

     // Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes) 
     imagePathReference.data(withMaxSize: 1 * 1024 * 1024) { data, error in 
      if error != nil { 
       // Uh-oh, an error occurred! 

      } else if data != nil { 

       // Data for the image is returned 

       // Save the image data into cache 
       CacheManager.putImageIntoCache(data: data!, imageName: imageName) 

       // Notify the delegate on the main thread 
       DispatchQueue.main.async { 

        // Notify the delegate 
        if let actualDelegate = self.delegate { 
         actualDelegate.firebaseManager?(imageName: imageName, imageData: data!) 
        } 
       } // End DispatchQueue 
      } 
     } 
    } 

    func closeObserversForPlace(placeId:String) { 

     // Remove observers from that place node 
     ref.child("meta").child(placeId).removeAllObservers() 
    } 



    // MARK: - Version Functions 

    func getVersionFromDatabase() { 

     // Get the version from the database 
     ref.child("version").observeSingleEvent(of: .value, with: { (snapshot) in 

      let versionString = snapshot.value as? String 

      if let databaseVersion = versionString { 

       let cachedVersion = CacheManager.getVersionFromCache() 

       if cachedVersion != nil { 

        // Compare the cached version number to the database version 
        if databaseVersion > cachedVersion! { 

         // Remove all cached data 
         CacheManager.removeAllCachedData() 
         CacheManager.putVersionIntoCache(version: databaseVersion) 
        } 
       } 
       else { 
        // Save the database version number to cache 
        CacheManager.putVersionIntoCache(version: databaseVersion) 
       } 

      } 

     }) 

    } 

    // MARK: - Helper Functions 

    func parsePlacesFrom(placesDict:NSDictionary) -> [Place] { 

     // Declare an array to store the parsed out places 
     var allPlaces = [Place]() 

     // Loop through all of the KVPs of the placesDict 
     for (placeid, placedata) in placesDict { 

      let placeDataDict = placedata as! NSDictionary 

      // Create a Place object for each and add it to an array to be returned 
      let place = Place() 

      place.id = placeid as! String 
      place.name = placeDataDict["name"] as! String 
      place.addr = placeDataDict["address"] as! String 
      place.lat = placeDataDict["lat"] as! Float 
      place.long = placeDataDict["long"] as! Float 
      place.type = PlaceType(rawValue: placeDataDict["type"] as! Int)! 
      place.cellImageName = placeDataDict["imagesmall"] as! String 

      place.createDate = placeDataDict["creationDate"] as! Int 



      // Put this place object into an array for returning 
      allPlaces += [place] 
     } 

     return allPlaces 

    } 

    func parseMetaFrom(metaDict:NSDictionary, place:Place) { 

     place.desc = metaDict["desc"] as! String 
     place.detailImageName = metaDict["imagebig"] as! String 

    } 

} // End class 

Antwort

1

Ok hier ist, was du musst mit beginnen zu verstehen:

Diese Methode mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) wird über den Pin aufgerufen, auf den Sie tippen, daher müssen Sie von dort aus mit Ihrer Logik umgehen.

Umzug auf, wie würden Sie tun, wenn MKAnnotationView Klasse so begrenzt ist und dessen Haupt Kind (annotation) nur liefert Ihnen die Grundlagen: pins' Koordinaten, Titel und Untertitel ... Einfach, 2 Optionen: Sie erstellen entweder eine benutzerdefinierte Klasse, die von ihr erbt, und Sie fügen Ihre benutzerdefinierten Parameter hinzu und können somit relevante Informationen, die Sie später verwenden, zu jedem Pin auf der Karte hinzufügen ODER (und ich denke, das könnte Seien Sie derjenige, den Sie wählen, da es die einfachste und wichtiger ist, die, die weniger Redundanz produziert) verwenden Sie Koordinaten aus solchen POI nt und später machen Sie eine Kreuzprobe von den Punktkoordinaten des ausgewählten Punktes und Ihrem Ortsmodell. Das wäre ziemlich aussehen etwas entlang der folgenden Zeilen:

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { 
    for p in places { 
     let testLocation = CLLocationCoordinate2D(latitude: CLLocationDegrees(p.lat), longitude: CLLocationDegrees(p.long)) 
     if testLocation.latitude == view.annotation!.coordinate.latitude && testLocation.longitude == view.annotation!.coordinate.longitude { 
      performSegue(withIdentifier: "mapSegue", sender: p) 
      break 
     } 
    } 
} 

override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
    let selectedPlace = sender as! Place //Careful with these force unwrapping in the future, I'm just using it here for simplicity but you should always double check them 

    let detailModel = DetailModel() 
    detailModel.place = selectedPlace 

    let detailVC = segue.destination as! VenueDetailViewController 
    detailVC.model = detailModel 
} 

Wie Sie sehen können, Ihre prepareForSegue Methode blieb so ziemlich das gleiche, außer dass jetzt nutzt sie den Sender-Parameter. Sie sollten auch vorsichtig sein, wenn Sie segue.destination als tun! VenueDetailViewController Da dies in der Zukunft zu weiteren Übergängen aus dieser Ansicht führen kann, kann dies zu Abstürzen aufgrund unerwarteter Parameter, die an andere Klassen gesendet werden, führen.

+0

Ich stimme zu. Weißt du, wie ich die aktuell ausgewählte Anmerkung auswählen kann? –

+0

Sie müssen die Logik in der 'mapView (_ mapView: MKMapView, AnnotationView-Ansicht: MKAnnotationView, calloutAccessoryControlTapped-Steuerelement: UIControl) bearbeiten'. Ich würde empfehlen, die Daten dort zu erhalten und erst danach 'prepare '(für segue: UIStoryboardSegue, sender: Any?) Aufzurufen und die Informationen über den _sender_-Parameter weiterzuleiten. Wie ich schon sagte: Wenn Sie mehr Hilfe von mir wollen, teilen Sie mehr Code oder noch besser: teilen Sie das gesamte Repo und ich werde es mir ansehen. Ich brauche mehr Kontext. –

+0

Okay danke, ich habe gerade meine Frage aktualisiert, um die gesamte Klasse plus meine Modell- und Firebasemanager-Klassen einzuschließen. Danke für Ihre Hilfe. –