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
Ich stimme zu. Weißt du, wie ich die aktuell ausgewählte Anmerkung auswählen kann? –
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. –
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. –