Search code examples
ioscocoa-touchswiftmkmapviewmkannotationview

Is it possible to have draggable annotations in Swift when using MapKit


I'm trying to give a user the ability to enter an address and have the map zoom to it which works.

I'd like to allow the user to drag the pin to another location then take the information of the location and use it for whatever.

I've tried and I've tried but the most I can get to work is changing the actual pin to a custom image. I've gone through several posts and tutorials (all for Objective-c) and even after converting code I still can't get this to work.

My custom annotation view:

import Foundation
import MapKit
class CustomAnnotationView: MKAnnotationView {

    var coordinate:CLLocationCoordinate2D!

    init(coordinate:CLLocationCoordinate2D) {
        super.init()
        self.coordinate = coordinate
    }

    override init(frame: CGRect) {
        super.init(frame: CGRect())
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(annotation: MKAnnotation!, reuseIdentifier: String!) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
        image = UIImage(named: "pin")
        draggable = true
        canShowCallout = true
    }

    func setCoordinate(newCoordinate: CLLocationCoordinate2D){
         self.coordinate = newCoordinate;
    }


}

My controller that houses my mapView:

import Foundation
import MapKit
class LocationViewController : UIViewController, MKMapViewDelegate {

    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var searchField: UITextField!
    @IBOutlet weak var addressLabel: UILabel!
    @IBOutlet weak var locationInstructions: UITextView!


    @IBAction func didTapGoButton(sender: UIButton) {

        var geocoder = CLGeocoder()
        geocoder.geocodeAddressString(searchField.text, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
            if let placemark = placemarks?[0] as? CLPlacemark {
                var region = self.mapView.region as MKCoordinateRegion
                region.center = placemark.location.coordinate
                region.span.longitudeDelta /= 30.0;
                region.span.latitudeDelta /= 30.0;

                self.mapView.zoomEnabled = true
                self.mapView.scrollEnabled = true
                self.mapView.addAnnotation(MKPlacemark(placemark: placemark))
                self.mapView.setRegion(region, animated: true)
            }

        })
    }

    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {

        if annotation.isKindOfClass(MKUserLocation) {
            return nil
        }

        var pin = mapView.dequeueReusableAnnotationViewWithIdentifier("CustomAnnotationView")

        if pin == nil {
            NSLog("PIN NIL")
            pin = CustomAnnotationView(annotation: annotation, reuseIdentifier: "CustomAnnotationView")

        }
        else
        {
            NSLog("PIN NOT NIL")
            pin.annotation = annotation;
        }

        return pin;
    }

    func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
        if newState == MKAnnotationViewDragState.Starting
        {
            view.dragState = MKAnnotationViewDragState.Dragging
        }
        else if newState == MKAnnotationViewDragState.Ending || newState == MKAnnotationViewDragState.Canceling
        {
            view.dragState = MKAnnotationViewDragState.None;
        }
    }

What am I missing?

I can't find any Swift examples where this was done successfully.


Solution

  • The settable coordinate property belongs in the object that implements the MKAnnotation protocol.

    The object that implements the MKAnnotation protocol is the one you pass to addAnnotation.


    The MKAnnotationView class (and your CustomAnnotationView subclass) are NOT the objects that implement MKAnnotation. They are the visual representation of the annotation model objects.


    Here's what the Location and Maps Programming Guide says:

    To implement minimal support for dragging:

    • In your annotation objects, implement the setCoordinate: method to allow the map view to update the annotation’s coordinate point.
    • When creating your annotation view, set its draggable property to YES.

    Your "annotation objects" are different things from your "annotation views".


    In didTapGoButton, the code is adding an annotation of type MKPlacemark which does not implement setCoordinate:.

    Instead, you'll need to use the built-in MKPointAnnotation class which does implement setCoordinate: or create your own custom class that implements the MKAnnotation protocol (not a subclass of MKAnnotationView) and provides a settable coordinate property.

    So instead of this:

    self.mapView.addAnnotation(MKPlacemark(placemark: placemark))
    

    do this:

    let pa = MKPointAnnotation()
    pa.coordinate = placemark.location.coordinate
    pa.title = placemark.name //or whatever title you like
    self.mapView.addAnnotation(pa)
    

    You can still keep your CustomAnnotationView class if you like but it's not necessary just to create an annotation view with a custom image and it definitely does not need a coordinate property.