swiftannotationsmapkitmklocalsearch

Swift - Directions to Selected Annotation from Current Location in Maps


My app currently has a local search which adds annotations for the search results. I want to set it up where when you select the annotation and click the call out button, it will open in the Maps application with directions to the annotation from the current device location. I'm having a few problems with this.

First of all, my call out button on the annotation does not appear. Secondly, I don't think I am detecting the selected annotation correctly.

Here is my code for the search and selected annotation:

func performSearch() {

    matchingItems.removeAll()
    let request = MKLocalSearchRequest()
    request.naturalLanguageQuery = searchText.text
    request.region = mapView.region

    let search = MKLocalSearch(request: request)

    search.startWithCompletionHandler({(response:
        MKLocalSearchResponse!,
        error: NSError!) in

        if error != nil {
            println("Error occured in search: \(error.localizedDescription)")
        } else if response.mapItems.count == 0 {
            println("No matches found")
        } else {
            println("Matches found")

            for item in response.mapItems as! [MKMapItem] {
                println("Name = \(item.name)")
                println("Phone = \(item.phoneNumber)")

                self.matchingItems.append(item as MKMapItem)
                println("Matching items = \(self.matchingItems.count)")

                var annotation = MKPointAnnotation()
                var coordinates = annotation.coordinate
                annotation.coordinate = item.placemark.coordinate
                annotation.title = item.name
                self.mapView.addAnnotation(annotation)

            }
        }
    })
}



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

        if self.mapView.selectedAnnotations?.count > 0 {

            if let selectedLoc = self.mapView.selectedAnnotations[0] as? MKAnnotation {
                println("Annotation has been selected")
                let currentLoc = MKMapItem.mapItemForCurrentLocation()
                let mapItems = NSArray(objects: selectedLoc, currentLoc)
                let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
                MKMapItem.openMapsWithItems([selectedLoc, currentLoc], launchOptions: launchOptions)
            }
        }

}

Any help will be appreciated, thanks in advance.


Solution

  • For the first issue:

    A callout button needs to be set explicitly in the viewForAnnotation delegate method (default red pins don't have one). Here's a simple example of one possible implementation:

    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
    
        if annotation is MKUserLocation {
            return nil
        }
    
        let reuseId = "pin"
    
        var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
        if pinView == nil {
            pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
            pinView!.canShowCallout = true
            pinView!.pinColor = .Purple
    
            //next line sets a button for the right side of the callout...
            pinView!.rightCalloutAccessoryView = UIButton.buttonWithType(.DetailDisclosure) as! UIButton
        }
        else {
            pinView!.annotation = annotation
        }
    
        return pinView
    }
    


    For the second issue:

    First, in calloutAccessoryControlTapped, the annotation is directly accessible using view.annotation so using the selectedAnnotations array in there is unnecessary.

    Next, openMapsWithItems expects an array of MKMapItem objects but in the array you are passing ([selectedLoc, currentLoc]), selectedLoc is not an MKMapItem -- it is just some object that implements MKAnnotation.

    Running this code will result in a crash with this error:

    -[MKPointAnnotation dictionaryRepresentation]: unrecognized selector sent to instance

    when the Maps app tries to use selectedLoc as if it was an MKMapItem.

    Instead, you need to create an MKMapItem from the selectedLoc annotation. This can be done by first creating an MKPlacemark from the annotation using MKPlacemark(coordinate:addressDictionary:) and then creating an MKMapItem from the placemark using MKMapItem(placemark:).

    Example:

    func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!,
        calloutAccessoryControlTapped control: UIControl!) {
    
            let selectedLoc = view.annotation
    
            println("Annotation '\(selectedLoc.title!)' has been selected")
    
            let currentLocMapItem = MKMapItem.mapItemForCurrentLocation()
    
            let selectedPlacemark = MKPlacemark(coordinate: selectedLoc.coordinate, addressDictionary: nil)
            let selectedMapItem = MKMapItem(placemark: selectedPlacemark)
    
            let mapItems = [selectedMapItem, currentLocMapItem]
    
            let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
    
            MKMapItem.openMapsWithItems(mapItems, launchOptions:launchOptions)
    }