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

1

12

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
}
Moseley answered 4/5, 2015 at 1:20 Comment(2)
possible duplicate of iOS Swift MapKit making an annotation draggable by the user?Tadtada
I thought I had this same issue, but it turned out I was just not touching at the right spot for dragging. It looks like the location to drag is very restricted (base of the pin - not the pin head).Leahleahey
C
15

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.

Crown answered 4/5, 2015 at 2:57 Comment(4)
Hey Anna, thank you so much! I've seen your comments on other posts, you are amazing! Should I edit my post above to reflect your changes, or should I let people with similar problems see my problem then your answer?Moseley
Thanks, please leave the question with the original code as reference. If the code in the question is changed, the answer looks strange (since now the code already "works"). Accepting the answer indicates the changes worked for you. However, if you like, you can add the updated code to your post showing both "before" and "after" but this isn't typically done.Crown
Just a side note... the coordinate property in PinAnnotationClass has to be var. In my case, I declared it as let coordinate and it did not work until I changed it to var.Bolster
@Anna thanks worked great, got some of my hairs pulled out until I found this one :) thanks againRhynchocephalian

© 2022 - 2024 — McMap. All rights reserved.