Version 3.2 with new sample for iOS 4.2 is available here.
Update 6: Version 3.1 with new sample for iOS 4.2 is available here.
Update 5: The MapKitDragAndDrop project now goes to version 3, and the whole project had shifted to modern runtime, using Objective-C 2.0 ABI and LLVM 1.5 compiler. So you will have smaller and faster binary that runs on both iOS 4 and ealier iPhone OS 3.1.x/3.2 at the same time.
There’re few samples about creating a MapKit app for iPhone, and Apple only provides snippets of code for people who want to dig out the secret of MapKit. So I decide to create my own MapKit sample that meets my needs. The sample code is available on my GitHub, MapKitDragAndDrop, feel free to download it.
Update 4: Check the new article: MapKitDragAndDrop 2.0
Update 3: This article is completely outdated, but the source code on github is always maintained, and had updated with thinner DDAnnotation design, CoreAnimation pin lift/drop/bounce effects just as we saw in Map.app. Make sure you check out the source code and give it a try.
Update 2: Thanks to Uffe Overgaard Koch’s help, the MapKitDragAndDrop now can update callout info automatically in both 3.0 and 3.1 SDK.
Update 1: In 3.0 SDK, the callout view info won’t update unless you tap to close it and tap pin again to bring it back. But in 3.1 SDK, the bug seems fixed, the MapKit will update the callout view info automatically when DDAnnotation changed.
In this sample, you can:
- Use CLLocationManager to find out current location
- Use MKReverseGeocoder to convert current location coordinate to place information
- Customize annotation/pin callout info
- Update callout info when MKPlacemark is found by MKReverseGeocoder
- And finally, allow annotation/pin to be able to drag and drop
The sample works pretty straight forward, it all begins by creating CLLocationManager to update location in UIViewController’s viewDidLoad:
Once CLLocationManager updates the location, we create DDAnnotation and add into the map view. Then we stop CLLocationManager from updating location again, otherwise you will see another new annotation drop on the map.
When we add newly created annotation to the map view, the MKMapViewDelegate methods take over. We prepare our own annotation view in mapView:viewForAnnotation:.
First, we avoid user’s current location annotation (MKUserLocation, the blue round spot you saw on the map) uses our custom annotation view. And then, we try to dequeue our annotation view from map view, if there’s no existing DDAnnotationView for us to reuse, we create a new one.
Finally, the tricky part, we assign the map view to DDAnnotationView before return. This is because when later user dragging the annotation/pin to new position, we will need to use map view’s convertPoint:toCoordinateFromView: to get the new coordinate.
MKAnnotationView and MKAnnotation
We create our own MKAnnotationView and MKAnnotation, and named them DDAnnotationView and DDAnnotation.
The DDAnnotation is designed to work like MKPlacemark or MKUserLocation, is a specific type of annotation that provides custom title and auto coordinate-to-placemark subtitle for DDAnnotationView, MKPinAnnotationView or other MKAnnotationView to use later.
And this is how DDAnnotation works: When you create DDAnnotation with initWithCoordinate:title:, we save the coordinate and title, and create MKReverseGeocoder to try to reverse coordinate.
Once MKReverseGeocoder converts the geocode to placemark information, MKReverseGeocoderDelegate mehod reverseGeocoder:didFindPlacemark: gets called, and we save the MKPlacemark, and post MKAnnotationCalloutInfoDidChangeNotification notification, to let the map view know about the change. Then, map view will ask DDAnnotationView to retrieve DDAnnotation’s title: and subtitle: and update the callout info.
As for DDAnnotationView, this is the part we handle drag and drop events and setup callout view. The event handling code was copied from Apple’s iPhone Application Programming Guide, you can check more detail from here.
Drag and Drop
But, basically, we implement all four touch event methods, which are touchesBegan: withEvent: for recording information about the initial touch event, let us know the start location of the movement, and touchesMoved:withEvent: method adjusts the position of the view by checking the new position to see if the dragging is actually happened.
When the user stops dragging an annotation view, the touchesEnded:withEvent: method takes over, we use map view’s convertPoint:toCoordinateFromView: to convert new pixel point back to map coordinate value, and set the new value back to this DDAnnotationView’s annotation object with DDAnnotation’s changeCoordinate: method. We are doing this was because MKAnnotation’s coordinate property is readonly, you are not allow to modify it by default, so we create a “setter” method to do the change.
And Lastly, the touchesCancelled:withEvent: method, this is not a optional method, if you decide to take care touch event, do not ignore this one (Apple said so). We reset the position and states here if draggin is not detected in ouchesMoved:withEvent:.
That’s it. Hope you enjoy the ride on the map.
Where to download
The source code is available on github.
- Craig Spitzkoff’s Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView.
- Gavi Narra’s Playing with Map Kit series: Part 1, Part 2 and Part 3.