I have a custom annotation view on the map, which has a UIButton
in it, but the UIButton
is not responsive when pressed. I have two main problems with user interaction on the annotation view:
- Buttons and other controls are not responsive.
- I want the annotation to block touches according to my implementation of
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
- that is if I return YES then I don't want the touches to get sent through to the MKMapView
(potentially selecting other annotations that are BEHIND my annotation view), I want to handle the touch myself in this case.
I have made sure userInteractionEnabled
is set to YES
and I have investigated how touches are sent to the custom annotation view (my subclass of MKAnnotationView
) by overriding touchesBegan
etc. - but it appears that the touches are usually cancelled (thought I've managed to get touchesEnded
a few times) - so it seems like it will even be difficult to manually implement any user-interaction with the custom annotation view.
Does anyone have any insights into allowing more user interaction with MKAnnotationView
objects?
I managed to resolve this with the help of a colleague. The solution is to override - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
. Our assumption is that MKAnnotationView
(which your annotation view must inherit from) overrides this to do the 'wrong' thing (presumably so that annotation selection doesn't get blocked between overlapping annotations). So you have to re-override it to do the right thing and return the appropriate UIView
, the system will then send the events to it and the user will be able to interact with it :). This has the beneficial (in this case) side-effect that the interactive annotation blocks the selection of annotations that are behind it.
I found that rather than overriding hitTest:withEvent:
I could just override pointInside:withEvent:
instead and just get it to return YES
. I guess that officially I should be doing a point-rect intersect check to ensure the place I'm tapping is within the control element, but in practise, just putting return YES
appears to work perfectly well, still allowing you to dismiss the MKAnnotationView by tapping away from it.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// for testing purposes
BOOL result = [super pointInside:point withEvent:event];
NSLog(@"pointInside:RESULT = %i", result);
return YES;
}
Adding up to the answer of jhabbott, this is what worked for me. I have a custom annotation view MKCustomAnnotationView
that holds a custom annotation CustomPin
as annotation. That 'pin' holds a UIButton
as accessory button replacement which I wanted to get touch events.
My hitTest
method would look like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *result = [super hitTest:point withEvent:event];
//NSLog(@"ht: %f:%f %d %@", point.x, point.y, [[event touchesForView:self] count], result);
if ([result isKindOfClass:[MKCustomAnnotationView class]])
{
MKCustomAnnotationView *av = (MKCustomAnnotationView *)result;
CustomPin *p = av.annotation;
UIButton *ab = p.accessoryButton;
if (p.calloutActive && point.x >= ab.frame.origin.x)
return ab;
}
return result;
}
The calloutActive
bool is probably not necessary in most cases.
For anyone looking to add a tapGesture to an AnnotationView subview then the answer at the bottom of this:
MKannotationView with UIButton as subview, button don't respond
Worked for me:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(_button.frame, point)) {
return _button;
}
return [super hitTest:point withEvent:event];
}