iOS Reference Library Apple Developer
Search

Legacy Map Techniques

Creating Draggable Annotations in Earlier Versions of iOS

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

Did this document help you? Yes It's good, but... Not helpful...