If you want to support draggable annotations in iOS 3.x, you must implement the code for tracking drag-related touches yourself. Although they live in a special layer above the map content, annotation views are full-fledged views capable of receiving touch events. You can use these events to detect hits within the view and initiate a drag operation.
Note: Because maps are displayed in a scrolling interface, there is typically a short delay between the time the user touches your custom view and the time corresponding events are delivered. This delay gives the underlying scroll view a chance to determine whether the touch event is part of a scrolling gesture.
The following sequence of code listings shows you how to implement a user-movable annotation view in iOS 3.x. In this example, the annotation view displays a bulls-eye image directly over the annotation’s coordinate point and includes a custom accessory view for displaying details about the target.
Listing A-1 shows the definition of the BullseyeAnnotationView
class. The class includes some additional member variables that it uses during tracking to move the view correctly. It also stores a pointer to the map view itself, the value for which is set by the code in the mapView:viewForAnnotation:
method when it creates or reinitializes the annotation view. The map view object is needed when event tracking is finished to adjust the map coordinate of the annotation object.
Listing A-1 The BullseyeAnnotationView
class
@interface BullseyeAnnotationView : MKAnnotationView |
{ |
BOOL isMoving; |
CGPoint startLocation; |
CGPoint originalCenter; |
MKMapView* map; |
} |
@property (assign,nonatomic) MKMapView* map; |
- (id)initWithAnnotation:(id <MKAnnotation>)annotation; |
@end |
@implementation BullseyeAnnotationView |
@synthesize map; |
- (id)initWithAnnotation:(id <MKAnnotation>)annotation |
{ |
self = [super initWithAnnotation:annotation |
reuseIdentifier:@"BullseyeAnnotation"]; |
if (self) |
{ |
UIImage* theImage = [UIImage imageNamed:@"bullseye32.png"]; |
if (!theImage) |
return nil; |
self.image = theImage; |
self.canShowCallout = YES; |
self.multipleTouchEnabled = NO; |
map = nil; |
UIButton* rightButton = [UIButton buttonWithType: |
UIButtonTypeDetailDisclosure]; |
[rightButton addTarget:self action:@selector(myShowAnnotationAddress:) |
forControlEvents:UIControlEventTouchUpInside]; |
self.rightCalloutAccessoryView = rightButton; |
} |
return self; |
} |
@end |
When a touch event first arrives in a bulls-eye view, the touchesBegan:withEvent:
method of that class records information about the initial touch event, as shown in Listing A-2. It uses this information later in its touchesMoved:withEvent:
method to adjust the position of the view. All location information is stored in the coordinate space of the superview.
Listing A-2 Tracking the view’s location
@implementation BullseyeAnnotationView (TouchBeginMethods) |
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
// The view is configured for single touches only. |
UITouch* aTouch = [touches anyObject]; |
startLocation = [aTouch locationInView:[self superview]]; |
originalCenter = self.center; |
[super touchesBegan:touches withEvent:event]; |
} |
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
UITouch* aTouch = [touches anyObject]; |
CGPoint newLocation = [aTouch locationInView:[self superview]]; |
CGPoint newCenter; |
// If the user's finger moved more than 5 pixels, begin the drag. |
if ( (abs(newLocation.x - startLocation.x) > 5.0) || |
(abs(newLocation.y - startLocation.y) > 5.0) ) |
isMoving = YES; |
// If dragging has begun, adjust the position of the view. |
if (isMoving) |
{ |
newCenter.x = originalCenter.x + (newLocation.x - startLocation.x); |
newCenter.y = originalCenter.y + (newLocation.y - startLocation.y); |
self.center = newCenter; |
} |
else // Let the parent class handle it. |
[super touchesMoved:touches withEvent:event]; |
} |
@end |
When the user stops dragging an annotation view, you need to adjust the coordinate of the original annotation to ensure the view remains in the new position. Listing A-3 shows the touchesEnded:withEvent:
method for the BullseyeAnnotationView
class. This method uses the map member variable to convert the pixel-based point into a map coordinate value. Because the coordinate
property of an annotation is normally read-only, the annotation object in this case implements a custom changeCoordinate
method to update the value it stores locally and reports using the coordinate
property. If the touch event was canceled for some reason, the touchesCancelled:withEvent:
method returns the annotation view to its original position.
Listing A-3 Handling the final touch events
@implementation BullseyeAnnotationView (TouchEndMethods) |
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
if (isMoving) |
{ |
// Update the map coordinate to reflect the new position. |
CGPoint newCenter = self.center; |
BullseyeAnnotation* theAnnotation = self.annotation; |
CLLocationCoordinate2D newCoordinate = [map convertPoint:newCenter |
toCoordinateFromView:self.superview]; |
[theAnnotation changeCoordinate:newCoordinate]; |
// Clean up the state information. |
startLocation = CGPointZero; |
originalCenter = CGPointZero; |
isMoving = NO; |
} |
else |
[super touchesEnded:touches withEvent:event]; |
} |
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
if (isMoving) |
{ |
// Move the view back to its starting point. |
self.center = originalCenter; |
// Clean up the state information. |
startLocation = CGPointZero; |
originalCenter = CGPointZero; |
isMoving = NO; |
} |
else |
[super touchesCancelled:touches withEvent:event]; |
} |
@end |
Last updated: 2010-05-20