4

I've made custom map callouts. My callouts contains UIButtons and UITextView. When I tap UIButton, it presses nice. But when I tap UITextView it moves cursor to tap position and then deselects pin and disappears callout...

I've implemented hitTest:withEvent: method of MyAnnotationView like here: https://stackoverflow.com/a/13495795/440168

But as I see in log, [super hitTest:withEvent:] never returns nil.

Here is my MyAnnotationView methods:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL isInside = [super pointInside:point withEvent:event];
    if (isInside)
        return YES;

    for (UIView * subview in self.subviews)
    {
        if ([subview isKindOfClass:[NSClassFromString(@"UICalloutView") class]])
            continue;

        CGPoint inPoint = [self convertPoint:point toView:subview];
        BOOL isInside = [subview pointInside:inPoint withEvent:nil];
        if (isInside)
            return YES;
    }

    return NO;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView * hitView = [super hitTest:point withEvent:event];
    if (hitView)
        return hitView;

    for (UIView * subview in self.subviews)
    {
        if ([subview isKindOfClass:[NSClassFromString(@"UICalloutView") class]])
            continue;

        CGPoint inPoint = [self convertPoint:point toView:subview];
        hitView = [subview hitTest:inPoint withEvent:event];
        if (hitView)
            return hitView;
    }

    return nil;
}

UPDATE 1:

Here is my code to add custom callout view:

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    for (UIView * subview in view.subviews)
        subview.hidden = YES;
    [view addSubview:self.myCalloutView];
    self.myCalloutView.center = CGPointMake(view.bounds.size.width/2,-self.myCalloutView.bounds.size.height/2);

    // ...
}

UPDATE 2:

I have just implemented my MKMapView subclass with durty hack. But this works!

@implementation HNPMapView

- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
    for (UIView * v in [self findSubviewsOfClass:[MyCallout class]]) {
        CGPoint point = [recognizer locationInView:v];
        if (CGRectContainsPoint(v.bounds, point))
            return;
    }

    //[super performSelector:@selector(handleTap:) withObject:recognizer];
    void (*functionPointer)(id,SEL,...) = [MKMapView instanceMethodForSelector:@selector(handleTap:)];
    functionPointer(self,@selector(handleTap:),recognizer);
}

@end

And using this category to find callout in view hierarchy:

@interface UIView (FindSubview)
- (NSArray *)findSubviewsOfClass:(Class)class;
@end
@implementation UIView (FindSubview)
- (NSArray *)findSubviewsOfClass:(Class)class
{
    NSMutableArray * found = [NSMutableArray array];
    for (UIView * subview in self.subviews)
    {
        if ([subview isKindOfClass:class])
            [found addObject:subview];
        [found addObjectsFromArray:[subview findSubviewsOfClass:class]];
    }
    return found;
}
@end
Community
  • 1
  • 1
k06a
  • 17,755
  • 10
  • 70
  • 110

1 Answers1

1

in hitTest method you should create a virtual rectangle to define touchable area according to your object to be touchable.

in UICalloutView you should use another loop to find your UITextView.

after that you should define this virtual rectangle according your UITextView dimensions and UICalloutView view origin points.

also you don't need to use continue statement in for loop, when return initiated all loops will stop immediately already.

so your hitTest method should be like this,

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView * hitView = [super hitTest:point withEvent:event];

    if (hitView)
        return hitView;

    for (UIView * subview in self.subviews)
    {

        if ([subview isKindOfClass:[NSClassFromString(@"UICalloutView") class]]) {

            for(UIView *subTextView in subview.subviews){
                if([subTextView isKindOfClass:[UITextView class]]){

                    CGRect touchableArea = CGRectMake(subview.frame.origin.x, subview.frame.origin.y, subTextView.frame.size.width, subTextView.frame.size.height);
                    if (CGRectContainsPoint(touchableArea, point)){
                        return subTextView;
                    }
                }               
            }            
        }
    }    
    return nil;
}
ytur
  • 1,232
  • 12
  • 22
  • I am setting breakpoint on `for` loop, but it never catches... I am not using UICalloutView, I am using my own - look at the question update. – k06a Jul 04 '13 at 11:40
  • It's wrong... in your code you add your cutomCallOutView under the pin image. you couldn't be add your customview directly in didSelectAnnotationView method. look at my example, I call another method in didSelectAnnotationView named "findCallOut" this method loop self and digg all "mapView" until to find currently opened callOutView and change it's properties and finaly add desired custom view as a subview. if you hide default bubble image and labels etc. after find UICallOutView run under another for loop to find subviews to hide. – ytur Jul 04 '13 at 13:05
  • I am using single custom callout view. I am trying to add it also to user location dot. Inside `didSelectAnnotationView` I add callout view to annotation view and inside `didDeselectAnnotationView` I remove callout from superview. Now I am trying to change annotation view frame and shift inner layers to stay pin at its place ... I have some problems but I can tap to my custom callout view. Seems problem in callout view frame out of parents frame.... – k06a Jul 04 '13 at 13:28