user3766930
user3766930

Reputation: 5829

Why my MKPointAnnotation not draggable and how can I change it?

I have the following code that fetches the gps coordinates from the user and puts a red marker in the place where he currently is:

class ViewController: UIViewController, CLLocationManagerDelegate {

    @IBOutlet var mapView: MKMapView!
    var locationManager: CLLocationManager?

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager = CLLocationManager()
        locationManager!.delegate = self

        if CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse {
            locationManager!.startUpdatingLocation()
        } else {
            locationManager!.requestWhenInUseAuthorization()
        }
    }

    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {


        switch status {
        case .NotDetermined:
            print("NotDetermined")
        case .Restricted:
            print("Restricted")
        case .Denied:
            print("Denied")
        case .AuthorizedAlways:
            print("AuthorizedAlways")
        case .AuthorizedWhenInUse:
            print("AuthorizedWhenInUse")
            locationManager!.startUpdatingLocation()
        }
    }

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        let location = locations.first!
        let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, 500, 500)
        mapView.setRegion(coordinateRegion, animated: true)
        locationManager?.stopUpdatingLocation()

        let annotation = MKPointAnnotation()
        annotation.coordinate = location.coordinate


        longitude = location.coordinate.longitude
        latitude = location.coordinate.latitude


        mapView.addAnnotation(annotation)


        locationManager = nil
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("Failed to initialize GPS: ", error.description)
    }
}

That works great and as you can see, I'm using these lines to get the user's lat and long to process it later on:

        longitude = location.coordinate.longitude
        latitude = location.coordinate.latitude

Now I want to add another functionality - user see his current location on the map as the red pin and can leave it as it is OR - the new feature - he can drag the red guy somewhere else and get its new longitude and latitude.

I've been looking here and there at the draggable feature and I've decided to add this code to my existing one:

func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
    if annotation is MKPointAnnotation {
        let pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myPin")

        pinAnnotationView.pinColor = .Purple
        pinAnnotationView.draggable = true
        pinAnnotationView.canShowCallout = true
        pinAnnotationView.animatesDrop = true

        return pinAnnotationView
    }

    return nil
}

But it didn't do the trick and the pin is still not draggable. How can I fix that and fetch (of for now - print to the console) the new location of the pin each time user grabs it and moves it around?

Upvotes: 1

Views: 840

Answers (2)

Rob
Rob

Reputation: 437372

If you're seeing a red marker rather than the purple marker you specified in your code sample, that would suggest that you haven’t registered your annotation view’s reuse identifier and/or the mapView(_:viewFor:) isn't getting called.

Nowadays, to help avoid view controller bloat, we’d generally put the customization of the annotation view in its own subclass (in the spirit of the “single responsibility principle”):

class CustomAnnotationView: MKPinAnnotationView {
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        pinTintColor = .purple
        isDraggable = true
        canShowCallout = true
        animatesDrop = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Then, in iOS 11 and later, you probably would remove the mapView(_:viewFor:) entirely and instead just register that class and you’re done:

override func viewDidLoad() {
    super.viewDidLoad()

    mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
}

In iOS versions prior to 11 (or if you have some complicated logic where you might have multiple types of annotations that you want to add) you’d want to specify the delegate of the map view and implement mapView(_:viewFor:).

One can hook up the delegate in IB by control-dragging from the delegate outlet in the “Connections Inspector” in IB, or by doing it programmatically, e.g. in viewDidLoad:

mapView.delegate = self

I’d then add a constant to specify my preferred the reuse identifier for the custom annotation view:

class CustomAnnotationView: MKPinAnnotationView {
    static let reuseIdentifier = Bundle.main.bundleIdentifier! + ".customAnnotationView"

    // these two are unchanged from as shown above

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) { ... }        
    required init?(coder aDecoder: NSCoder) { ... }
}

And then, you’d implement mapView(_:viewFor:) which will attempt to dequeue an previous annotation view and update its annotation, if possible, or instantiate a new annotation view, if not:

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if annotation is MKUserLocation { return nil }

        if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: CustomAnnotationView.reuseIdentifier) {
            annotationView.annotation = annotation
            return annotationView
        }

        return CustomAnnotationView(annotation: annotation, reuseIdentifier: CustomAnnotationView.reuseIdentifier)
    }
}

See previous iteration of this answer for older Swift versions.

Upvotes: 3

gklka
gklka

Reputation: 2564

Tip: You can drag an annotation when it is selected, and it can be selected only when it shows a callout. It can show a callout, when it has a title. So unless you have a title, it won’t work.

Upvotes: 0

Related Questions