Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing MKMapView changing selection (cleanly)

I have a custom subclass of MKPinAnnotationView that displays a custom call out. I want to handle touch events inside that annotation.

I have a working solution (below) but it just doesn't feel right. I have a rule of thumb that whenever I use performSelector: .. withDelay: I'm fighting the system rather than working with it.

Does anyone have a good, clean workaround for the aggressive event handling of MKMapView and annotation selection handling?

My current solution:

(All code from my annotation selection class)

I do my own hit testing (without this my gesture recognisers don't fire as the Map View consumes the events:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event; {
    // To enable our gesture recogniser to fire. we have to hit test and return the correct view for events inside the callout.

    UIView* hitView = nil;

    if (self.selected) {
        // check if we tpped inside the custom view
        if (CGRectContainsPoint(self.customView.frame, point))
            hitView = self.customView;
    }

    if(hitView) {
        // If we are performing a gesture recogniser (and hence want to consume the action)
        // we need to turn off selections for the annotation temporarily
        // the are re-enabled in the gesture recogniser.
        self.selectionEnabled = NO;

        // *1* The re-enable selection a moment later
        [self performSelector:@selector(enableAnnotationSelection) withObject:nil afterDelay:kAnnotationSelectionDelay];

    } else {
        // We didn't hit test so pass up the chain.
        hitView = [super hitTest:point withEvent:event];
    }

    return hitView;
}

Note that I also turn off selections so that in my overridden setSelected I can ignore the deselection.

- (void)setSelected:(BOOL)selected animated:(BOOL)animated; {
    // If we have hit tested positive for one of our views with a gesture recogniser,  temporarily 
    // disable selections through _selectionEnabled
    if(!_selectionEnabled){
        // Note that from here one, we are out of sync with the enclosing map view
        // we're just displaying out callout even though it thinks we've been deselected
        return;
    }

    if(selected) {
        // deleted code to set up view here
        [self addSubview:_customView];

        _mainTapGestureRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(calloutTapped:)];
        [_customView addGestureRecognizer: _mainTapGestureRecogniser];

    } else {
        self.selected = NO;
        [_customView removeFromSuperview];
    }


}

It's the line commented 1 that I don't like but it's also pretty hairy at the end of theta delayed fire. I have to walk back up the superview chain to get to the mapView so that I can convince it the selection is still in place.

// Locate the mapview so that we can ensure it has the correct annotation selection state after we have ignored selections.
- (void)enableAnnotationSelection {
    // This reenables the seelction state and resets the parent map view's idea of the
    // correct selection i.e. us.
    MKMapView* map = [self findMapView];
    [map selectAnnotation:self.annotation animated:NO];
    _selectionEnabled = YES;

}

with

-(MKMapView*)findMapView; {
    UIView* view = [self superview];
    while(!_mapView) {
        if([view isKindOfClass:[MKMapView class]]) {
            _mapView = (MKMapView*)view;
        } else if ([view isKindOfClass:[UIWindow class]]){
            return nil;
        } else{
            view = [view superview];
            if(!view)
                return nil;
        }
    }

    return _mapView;
}

This all seems to work without and downside (like flicker I've seen from other solutions. It's relatively straightforward but it doesn't feel right.

Anyone have a better solution?

like image 799
Rog Avatar asked Nov 10 '22 06:11

Rog


1 Answers

I don't think you need to monkey with the map view's selection tracking. If I'm correctly interpreting what you want, you should be able to accomplish it with just the hitTest:withEvent: override and canShowCallout set to NO. In setSelected: perform your callout appearance/disappearance animation accordingly. You should also override setHighlighted: and adjust the display of your custom callout if visible.

like image 123
jszumski Avatar answered Nov 15 '22 06:11

jszumski