Search code examples
iosswiftmkmapviewmkpinannotationviewmkmapviewdelegate

MKAnnotation Pin won't drag, even though set as draggable


I have been trying to put a draggable annotation on a mapview for ages. (I'm using the default pin, not my own) So far I can only display it at set coordinates (not much of an achievement, really) and I need to get the annotation first of all to react to being selected, it doesn't ever get received by the didChangeDragState func. Then I need to be able to drag it, place it in a new location and get the coordinates of the new location.

I'm reasonably new to Swift, but I've taken on a rather difficult project. I've looked at pretty much everything I could find on google looking for "draggable MKAnnotation mapkit in Swift" and similar variants.(edit: I hadn't found any answers that shed light on my problem, where all other answers gave responses for how to upload a personalized MKAnnotation. They all had title fields, but none of the answers mentioned that a title field was necessary, which turned out to be the main problem. They only mentioned that I should set the dragState to control the movement of the pin, but this turned out to be incorrect in my case as you see below) ANYWAY! Below is my code where I try to implement the mapView and add an annotation.

var currentLat:CLLocationDegrees!
var currentLong:CLLocationDegrees!
var currentCoordinate:CLLocationCoordinate2D! 
....
override func viewDidAppear(animated: Bool) {
    let annotation = PinAnnotationClass()
    annotation.setCoordinate(currentCoordinate)
    //annotation.setCoordinate(currentCoordinate)
    //AnnotationView()
    self.mapView.addAnnotation(annotation)
}

override func viewDidLoad() {
    super.viewDidLoad()
    println("hello!")
    self.mapView.delegate = self
    loadMap()

    findPath()
}

func loadMap()
{
    currentCoordinate = CLLocationCoordinate2DMake(currentLat, currentLong)
    var mapSpan = MKCoordinateSpanMake(0.01, 0.01)
    var mapRegion = MKCoordinateRegionMake(currentCoordinate, mapSpan)
    self.mapView.setRegion(mapRegion, animated: true)
}

Along with extension:

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

        if annotation is MKUserLocation {
            //return nil so map view draws "blue dot" for standard user location
            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!.draggable = true
                pinView!.annotation.coordinate
                pinView!.animatesDrop = true
                pinView!.pinColor = .Green
            }
            else {
                pinView!.annotation = annotation
            }

            return pinView
}

  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
    }
}

func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
    if let annotation = view.annotation as? PinAnnotationClass{
    }
}

I also have a custom PinAnnotation class:

import Foundation
import MapKit

class PinAnnotationClass : NSObject, MKAnnotation {
private var coord: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)

var coordinate: CLLocationCoordinate2D {
    get {
        return coord
    }
}

var title: String = ""
var subtitle: String = ""

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

Solution

  • The initial problem is that the annotation's title is not set.

    If an annotation's title is not set, it cannot be set to a "selected" state and it won't show a callout and didSelectAnnotationView will not get called.

    Since to drag an annotation, you have to first select it, you won't be able to drag it if the title is not set.

    So when creating the annotation, set its title to something:

    let annotation = PinAnnotationClass()
    annotation.title = "hello"
    annotation.setCoordinate(currentCoordinate)
    


    This should at least let you start dragging it but since you are using an MKPinAnnotationView instead of the plain MKAnnotationView, you do not need to implement didChangeDragState. In fact, if you do implement it when using MKPinAnnotationView, the annotation will not drag properly.

    In didChangeDragState, remove the code that sets view.dragState -- you don't need it when using MKPinAnnotationView.

    Instead, you can just log the coordinates where the annotation is dropped:

    func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
    
        if newState == MKAnnotationViewDragState.Ending {
            let ann = view.annotation
            print("annotation dropped at: \(ann!.coordinate.latitude),\(ann!.coordinate.longitude)")
        }
    }
    


    Unrelated, but the PinAnnotationClass implementation is more complicated than it needs to be. You don't need to write explicit get and set methods for coordinate. Just declare coordinate and the getter/setter will be done for you:

    class PinAnnotationClass : NSObject, MKAnnotation {
        var title: String = ""
        var subtitle: String = ""
        var coordinate: CLLocationCoordinate2D = kCLLocationCoordinate2DInvalid
        //kCLLocationCoordinate2DInvalid is a pre-defined constant
        //better than using "0,0" which are technically valid
    }
    

    You'll also need to change how the coordinate is assigned:

    //annotation.setCoordinate(currentCoordinate)  //old
    annotation.coordinate = currentCoordinate      //new
    


    Finally, also unrelated but this line in viewForAnnotation

    pinView!.annotation.coordinate
    

    means and does nothing, remove it.