我有一个我在MapView上显示的地方数组。我创建了一个MKAnnotationView来显示一个右键。然后我显示一个detailView并通过segue传递数据,但它显示错误的位置。我相信我的selectedAnnotations存在问题。用户一次只能选择一个注释。
全班
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
}
}
地点模型
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
答案 0 :(得分:1)
好的,这是你必须了解的内容:
此方法mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
通过您点击的 pin 调用,因此您需要从那里处理您的逻辑。
接下来,当MKAnnotationView
课程受到限制而且其主要孩子(annotation
)仅为您提供基本知识时,您将如何做到这一点:pin' 坐标,标题和字幕 ...简单,2个选项:您要么创建一个继承自它的自定义类,并添加自定义参数在其中,因此将能够包含您以后使用的相关信息到地图上的每个图钉或(我认为这可能是您选择的那个,因为它是' 最简单,更重要的是,产生较少冗余的那个)你使用来自这一点的坐标,然后根据所选择的点坐标和你的场所模型进行交叉匹配。这几乎与以下几行有关:
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
}
正如您所看到的,您的 prepareForSegue 方法几乎保持不变,但现在它利用了sender参数。在做 segue.destination时你也应该小心! VenueDetailViewController 因为将来如果从这个视图中添加更多的segue,这可能会导致由于意外的参数发送到其他类而导致崩溃。