Question

I would like to hide and show the navigation bar on tap like the one in the photos app BUT without losing the functionality of the MKMapView. the user should still be able to double tap for zoom, pinch and zoom and be able to select annotations.

I tried it with:

 UITapGestureRecognizer* tapRec = [[UITapGestureRecognizer alloc] 
                                  initWithTarget:self action:@selector(hideBar:)];
[self.myMKMapView addGestureRecognizer:tapRec];
[tapRec release];

But then the user can't select annotations anymore!And it also hides on double taps.

Any ideas ?

Was it helpful?

Solution

I know, it's almost exactly one year too late, but I hope somebody can make a use of it. Here's how I did it, based on @Cocoanetics's answer:

BOOL                mapReceivedDoubleTap;

...

UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapMap:)];
[tapGestureRecognizer setDelegate:self];
[_mapView addGestureRecognizer:tapGestureRecognizer];
[tapGestureRecognizer release];

...

// ignore annotations
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    return (![[touch view] isKindOfClass:[MKAnnotationView class]]);
}

// take care of double taps for zoom
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer  shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    if([otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        UITapGestureRecognizer *tr = (UITapGestureRecognizer *)otherGestureRecognizer;
        if(tr.numberOfTapsRequired == 2)
            mapReceivedDoubleTap = YES;
    }

    return NO;
}

- (void)didTapMap:(UITapGestureRecognizer *)tapGestureRecognizer {

    mapReceivedDoubleTap = NO;

    // hide/show on delay
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, .2f * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        if(!mapReceivedDoubleTap)
            [self.navigationController setNavigationBarHidden:!self.navigationController.navigationBarHidden animated:YES];
    });
}

Swift

import MapKit

class MapViewController: UIViewController, UIGestureRecognizerDelegate {

    @IBOutlet weak var myMapView: MKMapView!

    var mapReceivedDoubleTap = false

    override func viewDidLoad() {
        super.viewDidLoad()

        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MapViewController.didTapMap(_:)))            
        tapGestureRecognizer.delegate = self            
        myMapView.addGestureRecognizer(tapGestureRecognizer)
    }

    // ignore annotations
    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
        return !touch.isKindOfClass(MKAnnotationView)
    }

    // take care of double taps for zoom
    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer,
                           shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {

        if otherGestureRecognizer.isKindOfClass(UITapGestureRecognizer) {
            let tr = otherGestureRecognizer as! UITapGestureRecognizer
            if tr.numberOfTapsRequired == 2 {
                mapReceivedDoubleTap = true
            }
        }

        return false
    }

    func didTapMap(gestureRecognizer: UIGestureRecognizer) {

        mapReceivedDoubleTap = false

        // hide/show on delay
        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.2 * Double(NSEC_PER_SEC)))
        dispatch_after(popTime, dispatch_get_main_queue(), {
            if !self.mapReceivedDoubleTap {
                self.navigationController?.setNavigationBarHidden(!(self.navigationController?.navigationBarHidden)!, animated: true)
            }
        })
    }
}

OTHER TIPS

you probably need to to implement the delegate method for this gesture recognizer to detect simultaneously as the one on the MKMapView. Then you need to perform your hiding/showing on a delay and if an annotation gets selected you need to cancel this.

Alternatively you can do a hitTest in the delegate method that allows you to prevent touches from being delivered to your gesture if the hit view is an MKAnnotationView.

You can tell your gesture recognizer to trigger only if every gesture recognizer from the map fail.

 UITapGestureRecognizer* tapRec = [[UITapGestureRecognizer alloc] 
                                  initWithTarget:self action:@selector(hideBar:)];
for (UIGestureRecognizer recognizer in self.myMKMapView.gestureRecognizers) {
    [tapRec requireGestureRecognizerToFail:recognizer];
}
[self.myMKMapView addGestureRecognizer:tapRec];
[tapRec release];

I don't know it is a gestureRecognizer that handles the annotation though. Guess you'll have to try.

You can prevent a single click gesture recognizer from stealing the double click one with this code:

self.singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
singleTap.numberOfTapsRequired = 1;
for (UIGestureRecognizer* recognizer in self.mapView.gestureRecognizers) {
    if([recognizer isKindOfClass: [UITapGestureRecognizer class]] && ((UITapGestureRecognizer*)recognizer).numberOfTapsRequired == 2) {
        [singleTap requireGestureRecognizerToFail:recognizer];
    }
}
[self.mapView addGestureRecognizer:self.singleTap];

Can can prevent it from stealing other gestures in the same way.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top