iOS - Mkmapview - Draggable Annotations

How to Drag and drop the annotation pin in Mkmapview

To make an annotation draggable, set the annotation view's draggable property to YES.

This is normally done in the viewForAnnotation delegate method.

For example:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;

static NSString *reuseId = @"pin";
MKPinAnnotationView *pav = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (pav == nil)
{
pav = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
pav.draggable = YES;
pav.canShowCallout = YES;
}
else
{
pav.annotation = annotation;
}

return pav;
}

If you need to handle when the user stops dragging and drops the annotation,
see: how to manage drag and drop for MKAnnotationView on IOS?

how to manage drag and drop for MKAnnotationView on IOS?

In addition, your annotation object (the one that implements MKAnnotation) should have a settable coordinate property. You are using the MKPointAnnotation class which does implement setCoordinate so that part's already taken care of.

iOS Swift MapKit making an annotation draggable by the user?

It isn't enough to mark the annotation view by setting isDraggable to true. You must also implement mapView(_:annotationView:didChange:fromOldState:) in your map view delegate - and (even more important) this implementation must not be empty! Rather, your implementation must, at a minimum, communicate the drag state from the incoming parameters to the annotation view, like this:

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch newState {
case .starting:
view.dragState = .dragging
case .ending, .canceling:
view.dragState = .none
default: break
}
}

Once you do that, the annotation will be properly draggable by the user.

(Many thanks to this answer for explaining this so clearly. I can't claim any credit! My answer here is merely a translation of that code into Swift.)

iOS Mapkit Annotation is not getting draggable

In your case annotation is not an MKPointAnnotation type. Thats why its not entering into if annotation is MKPointAnnotation { condition.

It should be like:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

if annotation is pinAnnotation {

let pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myPin")
pinAnnotationView.pinTintColor = .purple
pinAnnotationView.isDraggable = true
pinAnnotationView.canShowCallout = true
pinAnnotationView.animatesDrop = true

return pinAnnotationView
}

return nil
}

Because annotation is pinAnnotation type.

And I can not see mapView.delegate = self in your code. But if you assign it via storyboard thats fine otherwise add it in viewDidLoad method.

And your complete code will be:

import UIKit
import MapKit

class ViewController: UIViewController,MKMapViewDelegate{
@IBOutlet weak var mapView: MKMapView!

override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
let centerLocation = CLLocationCoordinate2D(latitude: 12.964370125970357, longitude: 77.59643554937497)
let mapspan = MKCoordinateSpanMake(0.01, 0.01)
let mapregion = MKCoordinateRegionMake(centerLocation, mapspan)
self.mapView.setRegion(mapregion, animated: true)

let pin = pinAnnotation(title:"hello",subtitle:"how are you",coordinate:centerLocation)
mapView.addAnnotation(pin)

}

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

if annotation is pinAnnotation {

let pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myPin")
pinAnnotationView.pinTintColor = .purple
pinAnnotationView.isDraggable = true
pinAnnotationView.canShowCallout = true
pinAnnotationView.animatesDrop = true

return pinAnnotationView
}

return nil
}

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch newState {
case .starting:
view.dragState = .dragging
case .ending, .canceling:
view.dragState = .none
default: break
}
}
}

EDIT

You can get new location with view.annotation?.coordinate

check below code:

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch newState {
case .starting:
view.dragState = .dragging
case .ending, .canceling:
//New cordinates
print(view.annotation?.coordinate)
view.dragState = .none
default: break
}
}

How to make annotation point draggable in MapKit using swift 2 and xcode 7?

 pinAnnotationView.canShowCallout = false

This line solves my problem. Popup of showing "current location" was the main cause of this problem.

and these changes in code make it smooth dragging. Mehul and Vijay Answer help me in finding this solution.

func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch (newState) {
case .Ending, .Canceling:
view.dragState = .None
default: break
}
}

MKMapKit draggable annotation and drawing polygons

To make the pins draggable, you need to set draggable = true on the MKAnnotationView. Implement the viewForAnnotation and dequeue or create the annotation, then set draggable = true. Ensure that the MKMapView delegate is set otherwise none of the delegate methods will be called.

You may also find it easier to store the annotations in an array, rather than just storing the coordinates. The map view retains a reference to the annotations in the array, so when the point is moved in the map, the annotation is automatically updated.

Your question did not say whether you need to draw a path around the points, or through the points. If you want to draw an overlay which surrounds the points, then you also need to calculate the convex hull for the coordinates. The code example does this, although it's easily removed.

Example:

class MapAnnotationsOverlayViewController: UIViewController, MKMapViewDelegate {

@IBOutlet var mapView: MKMapView!

// Array of annotations - modified when the points are changed.
var annotations = [MKPointAnnotation]()

// Current polygon displayed in the overlay.
var polygon: MKPolygon?

override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
addLongPressGesture()
}

func addLongPressGesture() {
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPressRecogniser.minimumPressDuration = 0.25
mapView.addGestureRecognizer(longPressRecogniser)

}

func handleLongPress(gestureRecognizer: UIGestureRecognizer) {

guard gestureRecognizer.state == .Began else {
return
}

let touchPoint = gestureRecognizer.locationInView(self.mapView)
let touchMapCoordinate = mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)

let annotation = MKPointAnnotation()

// The annotation must have a title in order for it to be selectable.
// Without a title the annotation is not selectable, and therefore not draggable.
annotation.title = "Point \(annotations.count)"
annotation.coordinate = touchMapCoordinate
mapView.addAnnotation(annotation)

// Add the new annotation to the list.
annotations.append(annotation)

// Redraw the overlay.
updateOverlay()
}

@IBAction func drawAction(sender: AnyObject) {
updateOverlay()
}

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

var view = mapView.dequeueReusableAnnotationViewWithIdentifier("pin")

if let view = view {
view.annotation = annotation
}
else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")

// Allow the pin to be repositioned.
view?.draggable = true
}

return view
}

func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {

// The map view retains a reference to the same annotations in the array.
// The annotation in the array is automatically updated when the pin is moved.

updateOverlay()
}

func updateOverlay() {

// Remove existing overlay.
if let polygon = self.polygon {
mapView.removeOverlay(polygon)
}

self.polygon = nil

if annotations.count < 3 {
print("Not enough coordinates")
return
}

// Create coordinates for new overlay.
let coordinates = annotations.map({ $0.coordinate })

// Sort the coordinates to create a path surrounding the points.
// Remove this if you only want to draw lines between the points.
var hull = sortConvex(coordinates)

let polygon = MKPolygon(coordinates: &hull, count: hull.count)
mapView.addOverlay(polygon)

self.polygon = polygon
}

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = UIColor.blackColor()
polygonView.lineWidth = 0.5
return polygonView
}
return MKPolylineRenderer()
}
}

Here is the convex hull sorting algorithm (adapted from this Gist on GitHub).

func sortConvex(input: [CLLocationCoordinate2D]) -> [CLLocationCoordinate2D] {

// X = longitude
// Y = latitude

// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
// Returns a positive value, if OAB makes a counter-clockwise turn,
// negative for clockwise turn, and zero if the points are collinear.
func cross(P: CLLocationCoordinate2D, _ A: CLLocationCoordinate2D, _ B: CLLocationCoordinate2D) -> Double {
let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude)
let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude)
return part1 - part2;
}

// Sort points lexicographically
let points = input.sort() {
$0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude
}

// Build the lower hull
var lower: [CLLocationCoordinate2D] = []
for p in points {
while lower.count >= 2 && cross(lower[lower.count-2], lower[lower.count-1], p) <= 0 {
lower.removeLast()
}
lower.append(p)
}

// Build upper hull
var upper: [CLLocationCoordinate2D] = []
for p in points.reverse() {
while upper.count >= 2 && cross(upper[upper.count-2], upper[upper.count-1], p) <= 0 {
upper.removeLast()
}
upper.append(p)
}

// Last point of upper list is omitted because it is repeated at the
// beginning of the lower list.
upper.removeLast()

// Concatenation of the lower and upper hulls gives the convex hull.
return (upper + lower)
}

This is how it would look with the convex hull sorting (path drawn around points):

Convex hull surrounding points

This is how it looks without sorting (path drawn from point to point in sequence):

Point-to-point path

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

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.

How Can I Drag My Annotation On MKmap View Like Google Pin Drop And Draging:iOS

First allow pin to drag

- (MKAnnotationView *) mapView: (MKMapView *) mapView viewForAnnotation: (id) annotation 
{
pin.draggable = YES;
}

- (void)mapView:(MKMapView *)mapView
annotationView:(MKAnnotationView *)annotationView
didChangeDragState:(MKAnnotationViewDragState)newState
fromOldState:(MKAnnotationViewDragState)oldState
{
if (newState == MKAnnotationViewDragStateEnding)
{
CLLocationCoordinate2D droppedAt = annotationView.annotation.coordinate;
[annotationView.annotation setCoordinate:droppedAt];
if(DEBUG_MODE){
NSLog(@"Pin dropped at %f,%f", droppedAt.latitude, droppedAt.longitude);
}
}

}


Related Topics



Leave a reply



Submit