2017-12-28 4 views
0

MapView에 표시 할 장소의 배열이 있습니다. 오른쪽 버튼을 표시하기 위해 MKAnnotationView를 만들었습니다. 그런 다음 detailView를 표시하고 segue를 통해 데이터를 전달하지만 잘못된 위치를 표시하고 있습니다. 내가 선택한 애너테이션에 문제가 있다고 생각합니다. 사용자는 한 번에 하나의 주석 만 선택할 수 있습니다.MKAnnotation View Segue

전체 클래스

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 

답변

1

좋아, 여기에 당신이 시작하는 이해 돼있는 작업은 다음과 같습니다

이 방법 mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)을 통해 호출되므로 거기에서 논리를 처리해야합니다.

에 이동, 당신이 얼마나 그 MKAnnotationView 클래스가 너무 제한되어 있으며 주요 자녀 (annotation가)에만 기초를 제공 할 때 : 핀 ' 좌표, 제목자막 ... 쉬운, 2 가지 옵션 : 당신은 그것으로부터 상속받은 커스텀 클래스를 만들고 거기에 커스텀 매개 변수를 추가하면 나중에지도의 모든 핀에 사용할 관련 정보를 포함 할 수 있습니다. 또는 (아마도 이것은 중 가장 쉬운 숫자가 이상이므로 선택하는 사람이 되십시오. 더 중요한 것은 적은 양의 중복성을 생성하는 것입니다. 이러한 poi의 좌표를 사용하십시오. nt 이후에 선택한 좌표와 점 모델에서 교차 일치를 만듭니다. 당신이 볼 수 있듯이

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 방법은 꽤 많은 것을 제외하고 같은 머물렀다 지금은 보낸 사람 매개 변수의 활용 : 그 것 거의 다음과 같은 라인을 따라 뭔가를 찾습니다. segue.destination을 다음과 같이 할 때 조심해야합니다! VenueDetailViewController 앞으로이 뷰에서 더 많은 segues를 추가하면 예기치 않은 매개 변수가 다른 클래스로 전송되기 때문에 충돌이 발생할 수 있습니다.

+0

동의합니다. 현재 선택된 주석을 어떻게 선택할 수 있는지 알고 있습니까? –

+0

'mapView (_ mapView : MKMapView, annotationView view : MKAnnotationView, calloutAccessoryControlTapped 컨트롤 : UIControl) 내부에서 로직을 조작해야합니다. ' 나는 거기서 데이터를 얻고 나중에'prepare (segue : UIStoryboardSegue, 발신자 : Any?) '를 호출하여 _sender_ 매개 변수를 통해 정보를 전달하는 것이 좋습니다. 전에 말했듯이 : 나에게 더 많은 도움이 필요하면 더 많은 코드를 공유하거나 더 나아질 수 있습니다. 전체 레포를 공유하면 살펴볼 것입니다. 나는 더 많은 문맥이 필요하다. –

+0

좋아, 고마워, 난 내 모델과 firebasemanager 클래스 플러스 전체 클래스를 포함하도록 내 질문을 업데이 트했습니다. 당신의 도움을 주셔서 감사합니다. –