7

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:

  1. Buttons and other controls are not responsive.
  2. 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?

jhabbott
  • 18,461
  • 9
  • 58
  • 95
  • Are you able to post some code as to how you are adding your UIControls to the MKAnnotationView? – Ricky Aug 04 '11 at 23:15
  • 1
    its better to put buttons in the callout, not the annotation – Bushra Shahid Aug 10 '11 at 09:03
  • @Pennypacker I have a UView with the controls nested inside it, this UIView is also used elsewhere in the app. The UIView is added to my custom annotation view. – jhabbott Oct 24 '11 at 17:06
  • @xs2bush Callouts can't be customised like Annotations can, this needs to be done with Annotations. – jhabbott Oct 24 '11 at 17:06
  • i have added buttons to callout as right or left accessory view...it totally works – Bushra Shahid Oct 26 '11 at 09:58
  • @xs2bush This question/solution is about using annotations as callouts, instead of standard callouts so that you can get a very unique look to your callouts. – jhabbott Oct 26 '11 at 11:42
  • Hi, could you add a TouchDown event, I am getting those but not TouchUpInside, in fact I can get touch up inside but I need to press the button, move my finger outside its bounds, then back in and release OR press and hold for a few seconds and then release. This is being caused by the MKMap's interaction listening for pinches, swipes, taps etc, I think you might need to disable the map when your UIControl receives a touch down, and reenable it on touch up. This way you may be able to intercept the touch up inside events. – Daniel Apr 16 '12 at 15:02

4 Answers4

5

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.

jhabbott
  • 18,461
  • 9
  • 58
  • 95
  • I'm still trying to work out how to implement this myself... Do you override the `hitTest:withEvent:` inside the `MKAnnotationView`, or the `MKMapView`? If it's the `MKMapView` does that mean I need to subclass it in order to override the method? Which view do you need to return, the `rightCalloutAccessoryView` or the `MKAnnotationView`? – jowie Feb 21 '12 at 09:48
  • 1
    Subclass `MKAnnotationView` with your custom view class and override `hitTest:withEvent:` - from here you can return the view itself, or one of its subviews (for example a button or other interactive object) - touches will be sent to that view. – jhabbott Feb 21 '12 at 10:43
  • strangely, that doesn't work for me. The only thing I can get to work is to implement `pointInside:withEvent` and return `YES`. Even then, it works okay with a normal pin annotation, but neither methods work with the user location pin annotation. – jowie Feb 22 '12 at 16:11
0

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.

jimpic
  • 5,360
  • 2
  • 28
  • 37
0

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];
}
Community
  • 1
  • 1
Morgz
  • 574
  • 6
  • 15
0

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;
}
jowie
  • 8,028
  • 8
  • 55
  • 94
  • If you only override pointInside:withEvent, then any other annotations *behind* your interactive annotation can still be selected, thus de-selecting the active one. You need to override hitTest:withEvent in order to block the touches being sent to other overlapping (i.e. behind) annotations. – jhabbott Oct 24 '11 at 17:01
  • So when you override `pointInside:withEvent:` do you just return nil in order to block it from bubbling? – jowie Oct 24 '11 at 21:07
  • No, I assume that by 'bubbling' you mean the callout popping up? My question/solution is for when you're not using callouts at all (because they are not very customisable) and using custom annotations instead (adding the custom annotation when one of your other 'pin' annotations is selected). – jhabbott Oct 26 '11 at 09:52