MKAnnotation 视图转场

MKAnnotation View Segue

我有一组要显示在 MapView 上的地点。我创建了一个 MKAnnotationView 来显示一个右键。然后我显示一个 detailView 并通过 segue 传递数据,但它显示错误的位置。我认为我的 selectedAnnotations 有问题。用户在任何时候只能 select 一个注释。

整个Class

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

好的,这是您开始时需要了解的内容:

此方法 mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) 通过您点击的 pin 调用,因此您需要从那里处理您的逻辑。

继续前进,当 MKAnnotationView class 如此有限且其主要子项 (annotation) 仅为您提供基础知识时,您将如何做到这一点:引脚 coordinatestitlesubtitle... 简单,2 个选项:您可以自定义 class继承自它并在其中添加自定义参数,因此将能够包含您稍后将用于地图上每个图钉的相关信息 OR(我认为这可能是您选择的那个,因为它是 最简单的 并且更重要的是,它产生的冗余更少)您使用来自该点的坐标,然后根据所选点的坐标和您的坐标进行交叉匹配放置模型。这看起来很像以下几行:

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 as 时你也应该小心! VenueDetailViewController 因为,如果将来从此视图添加更多 segue,这可能会由于从其他 classes 发送的意外参数而导致崩溃。