Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make a MKAnnotationView touch sensitive?

I currently have a map view with some annotation on it. I have the annotation with custom images. The problem I am trying to fix is the sensitivity of the images. When I try to drag them, it feels like I have to touch the exact center for it to be focused on. Is there a way to make the touch bounds bigger?

like image 562
Phong Le Avatar asked Dec 27 '11 19:12

Phong Le


1 Answers

To do this, you need to subclass MKAnnotationView to create your own custom MKAnnotationView. In your subclass, override the following functions:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
    // Return YES if the point is inside an area you want to be touchable
}

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    // Return the deepest view that the point is inside of.
}

This allows interactive views (such as buttons, etc.) to be pressed. The default implementation in MKAnnotationView is not strict on pointInside and hitTest because it allows presses that are actually inside one annotation to get sent to a different annotation. It does this by figuring out the closest annotation center to the touch-point and sending the events to that annotation, this is so that close-together (overlapping) annotations don't block each other from being selected. However, in your case you probably want to block other annotations if the user is to select and drag the topmost annotation, so the above method is probably what you want, or else it will set you on the right path.

EDIT: I was asked in comments for an example implementation of hitTest:withEvent: - This depends on exactly what you are trying to achieve. The original question suggested touching and dragging images within the annotation whereas in my case I have some buttons inside the annotation that I want to be interactive. Here's my code:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* hitView = [super hitTest:point withEvent:event];
    if (hitView != nil)
    {
        // ensure that the callout appears above all other views
        [self.superview bringSubviewToFront:self];

        // we tapped inside the callout
        if (CGRectContainsPoint(self.resultView.callButton.frame, point))
        {
            hitView = self.resultView.callButton;
        }
        else if (CGRectContainsPoint(self.resultView.addButton.frame, point))
        {
            hitView = self.resultView.addButton;
        }
        else
        {
            hitView = self.resultView.viewDetailsButton;
        }
        [self preventSelectionChange];
    }
    return hitView;
}

As you can see it's quite simple - The MKAnnotationView implementation (called as super on the first line of my implementation) only returns the first (outermost) view, it does not drill down through the view hierarchy to see which sub-view the touch is actually inside. In my case I just check if the touch is inside one of three buttons and return those. In other circumstances you may have simple rectangle-based drilling down through the hierarchy or more complex hit tests for example to avoid transparent areas within your view to allow touches to pass through those parts. If you do need to drill down, CGRectContainsPoint can be used the same way I have used it, but remember to offset your points into local view coordinates for each view-level you drill into.

The preventSelectionChange method is to prevent my custom annotation from becoming selected, I am using it as a customisable/interactive callout from map pins and this keeps the pin it relates to selected instead of allowing the selection to change to this annotation.

like image 117
jhabbott Avatar answered Oct 05 '22 23:10

jhabbott