Detecting touches on MKOverlayView

So you’ve used MKOverlayView to add a Google Map to your app and you’ve added to it a series of MKOverlays and implemented mapView:viewForOverlay: which returns the corresponding MKOverlayView for your overlay. But the problem is that now you want to detect touch events on the MKOverlayView so that an action can be taken depending on which view is tapped.

This seems to be one of those questions that many have asked, but has not really received any successful answer! Well, there is actually a way to detect touches on an MKOverlayView, and it’s surprisingly simple!

The first thing you need to do is set up an NSMutableArray as an ivar to store all MKOverlayViews currently visible on the screen. The simplest thing to do here would be would be to to just store every MKOverlayView you create in mapView:viewForOverlay: in this array. The problem is that if you keep adding (and never removing) overlays as the map moves, your array will keep growing and growing which may cause performance issues in what comes later.

So now you have an ivar (let’s call it overlayViews) which contains all the MKOverlayViews currently visible on the map (it may also contain other MKOverlayViews that are no longer visible if you go with the simple approach).

Next thing you need to do is create a UIGestureRecognizer and add it to the MKMapView (which I assume is called mapView). You can do this in viewDidLoad. Simply add the following three lines:

    UIGestureRecognizer *gestureRecognizer = [[GestureRecognizer alloc] init];
    gestureRecognizer.delegate = self;
    [mapView addGestureRecognizer:gestureRecognizer];

UIGestureRecognizer is a class used to handle touch gestures, and by setting the delegate to self and adding it as a gesture recogniser to the MKMapView, which means that all touch events will now fire a delegate method on self.

This would be a good time to modify your header file to implement UIGestureRecognizerDelegate. While you’re there, declare a new ivar called dragging of type BOOL – we’ll explain why in a moment. Back in your main file, you can now implement the UIGestureRecognizerDelegate methods:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  {
    dragging = NO;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    dragging = YES;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (!dragging) {
        if ([touches count] == 1) {
            UITouch *touch = [touches anyObject];
            for (MKOverlayView *overlayView in overlayViews) { // this is where things can get inefficient if you have loads of overlayViews that aren't even visible!
                CGSize overlayViewSize = overlayView.frame.size;

                CGPoint touchPoint = [touch locationInView:overlayView];

                if (touchPoint.x > 0 && touchPoint.x < overlayViewSize.width && touchPoint.y > 0 && touchPoint.y < overlayViewSize.height) {
                    [self overlayTouched:overlayView]; // overlayView is the one that was touched!
                }
            }
        }
    }
}

Let’s go through this step-by-step. Firstly, why did we need the dragging BOOL? Well, not only do you want to be able to handle touch events for the MKOverlayView, you also will likely want to still allow the user to scroll and pinch-zoom on the map. If you simply directed all touch events to the MKOverlayView, you would end up with a situation that even touching and dragging at a point on the map over an MKOverlayView would result in a touch event being triggered on the MKOverlayView, which is probably not what you wanted. Therefore, the dragging boolean keeps track of whether the user is dragging their finger across the screen, and if so, the touch event is ignored and is just handled by the MKMapView. That’s why in touchesBegin:withEvent: we set dragging = NO, and when touchesMoved:withEvent: we update dragging = YES. If touchesBegin:withEvent: is followed immediately by touchesEnded:withEvent: then dragging remains NO and we know that it was a tap, rather than a drag.

So once we know that the touch event is indeed a tap and not a drag, we then iterate over all of the MKOverlayViews that we stored previously in overlayViews, and for each one we compare the coordinates of the touch event against the coordinates of the overlayView. If the touch event lies within the frame of the overlayView then we know that that is the MKOverlayView that was touched, and can take action appropriately.

That action could be handled in the class itself, or you might choose to subclass MKOverlayView so that the view can handle its own events, and then call a method on it (e.g. [overlayView touched]) where appropriate.

33 Responses to “Detecting touches on MKOverlayView”

  1. [...] Originally Posted by vertine in fact, no. i never did- would still love to know this one Solved: Detecting touches on MKOverlayView Jonathan Ellis [...]

  2. Keerthi says:

    Thanks a lot for your very understandable explanation. It really helped me a lot while i was looking for a solution long.

  3. Keerthi says:

    Could you please tell me how to store MKOverlayViews currently visible on the screen?

  4. jon says:

    Keerthi, thanks for your message. Regarding your question, the simplest (and most naive) way would, as I mentioned, to create an NSMutableArray as an instance variable and keep adding to it whenever you create a new view in mapView:viewForOverlay:.

    Obviously this is inefficient because you will keep adding more and more views without ever removing any. Eventually, as the user pans around the map, you could end up clogging up the array with hundreds or even thousands of overlays and end up crashing the app.

    One way around this would be to set-up a timer that periodically goes through the array and checks which views are no longer visible and then removes them from the array.

    However, there are much more clever ways of doing it. One way is to look at how UITableView handles storing UITableViewCells. If you’ve ever used that part of the SDK, you will know that the table view “dequeues” existing cells and re-uses them wherever possible in order to make efficient use of memory.

    It should be possible to apply exactly the same strategy here in order to recycle the MKOverlayViews so that memory is used most efficiently and the code runs as quickly as possible. This might be something I address in a future blog post. If in the meantime you work out a solution to this then please do post it here!

  5. topgiftie@gmail.com says:

    this is not work form me.
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    not fire. can you send me the hold sample project to email:topgiftie@gmail.com

  6. baboy says:

    hi, I have a same question, it is not work form me.
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    this functions not fire, can u help me ?

  7. Son of a Beach says:

    These methods are NOT UIGestureRecognizerDelegate methods:

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

    The ONLY UIGestureRecognizerDelegate methods are:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

    (See Apple’s doco at: http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIGestureRecognizerDelegate_Protocol/Reference/Reference.html )

    I don’t think the UIGestureRecognizer is actually of any use in this case, and is just confusing the issue.

    The three methods you’ve listed are methods built into the UIResponder class (see http://developer.apple.com/library/ios/#documentation/uikit/reference/UIResponder_Class/Reference/Reference.html). This means they are built into every UIView class and subclass (which inherits from UIResponder).

  8. Son of a Beach says:

    PS. The UIResponder documentation also states that, “If you override [a touch-handling] method without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empy) implementations.” Which menas that you should also override the touchesCancelled:withEvent: method.

  9. Son of a Beach says:

    PPS. For those people who can’t get the touchesXxxx:withEvent: methods to work, it may be because you’re using them as delegate methods (which they are not) and not including them in a MKMapView subclass (or at least in a UIView subclass) which is where they need to be.

  10. jon says:

    Son of a Beach,

    You are 100% correct — I did indeed make a mistake with the UIGestureRecognizer code, and didn’t include an important step. In fact, it is necessary to subclass UIGestureRecognizer in order to get the correct functionality, and forward the touches... events properly.

    That’s where the confusion comes from, and I suspect most of the issues posted in these comments. I will amend the post later on today.

    Thanks for pointing that out.

  11. Upholstery Cleaning Irvine says:

    Thanks for the auspicious writeup. It actually was a amusement account it. Look complex to far introduced agreeable from you! By the way, how can we communicate?

  12. Fabrizio Bartolomucci says:

    I used your solution thanking you for that but I bounced in crash:
    -[MKPolyline frame]: unrecognized selector sent to instance 0x24780a40′

    this is how I inserted your code:

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (!dragging) {
    if ([touches count] == 1) {
    UITouch *touch = [touches anyObject];
    for (MKOverlayView *overlayView in self.myMapView.overlays) { // this is where things can get inefficient if you have loads of overlayViews that aren’t even visible!
    CGSize overlayViewSize = overlayView.frame.size;

    CGPoint touchPoint = [touch locationInView:overlayView];

    if (touchPoint.x > 0 && touchPoint.x 0 && touchPoint.y < overlayViewSize.height) {
    [self overlayTouched:overlayView]; // overlayView is the one that was touched!
    }
    }
    }
    }
    }

    The overlays in the mapview are in fact polylines.

    How may I fix it?

    Thanks, Fabrizio

  13. Thomas says:

    Hi,

    Thank you for this great example.

    PS : on your first piece of code, it’s [UIGestureRecognizer alloc] ;)

  14. homesite says:

    My cousin recommended I’d possibly like that site. He / she once was 100 % proper. The following set up really produced my working day. You actually cann’t imagine purely that the ton period I’d spent for this information and facts! Thanks a lot!

  15. Ric says:

    This post is misleading, as the UIGestureRecognizer is not used and UIViewControllers do not receive touch events.

    The correct answer has been posted here: http://stackoverflow.com/a/8154639/883413

  16. e-juice says:

    It really is an excellent plus helpful little bit of information and facts. I’m joyful that you distributed this handy data here. Please remain you educated in this way. Many thanks for sharing.

  17. Visit my Site says:

    Amazing difficulties altogether, you just accumulated the latest reader. What can a person recommend in regards to the submit that you made a 1 week back? Any kind of convinced?

  18. Fabrizio Bartolomucci says:

    I fixed the issue by interspersing arrow annotations in the overlay and detecting their touches.

  19. Ermelinda says:

    Fastidious answers in return of this question with firm arguments and explaining everything about that.

    Also visit my web site; Jessica’s Shoes

  20. Krista says:

    I think this is among the most significant information for me.
    And i’m glad reading your article. But should remark on some general things, The site style is
    perfect, the articles is really great : D. Good job,
    cheers

    My web site … sources

  21. Alvaro says:

    We are a group of volunteers aand starting a new scheme in our community.
    Your web siite provided us with valuable information to work on.
    You’ve done a formidable job and our whole
    community will be thankful to you.

    Feel free to visit my web blog :: Wedding Heels

  22. Ardis says:

    Thanks to my father who informed me regarding this webpage, this weblog is really amazing.

    Feel free to visit my webpage: noritz tankless water heater (Ardis)

  23. water heater says:

    You know consequently substantially in regards to this topic, made me personally in my view accept it as true via quite a few various sides. Its like people are not fascinated until it’s something to complete using Lady crazy! Your personal items outstanding. Usually take care of that!

  24. Ryan says:

    I hardly drop remarks, but i did some searching and wound up here Jonathan
    Ellis ? Detecting touches on MKOverlayView. And I actually do have a couple of questions for you if it’s
    allright. Could it be only me or does it appear like a few of these comments appear like they are left by brain dead individuals?
    :-P And, if you are posting at additional online social sites, I would like
    to follow anything new you have to post.
    Would you make a list of the complete urls of your public
    sites like your twitter feed, Facebook page or linkedin profile?

    Also visit my web site: Drawing is fun

  25. furniture steam cleaner says:

    Here is an easy tip to ensure that your furniture and furniture remains clean and lovely in between expert furniture cleaning. When it comes time to tidy upholstery, use just natural cleaners. Before getting a potential business to wash your carpets and carpets, it’s clever to look into the track record of this company.

  26. Jaclyn says:

    Hii! What is the nae off that blog template? Or it’s custom?

    Also visit my web page – Xbox 360 emulator (Jaclyn)

  27. Brianna says:

    ?i to all, it’s in fact a good for me to go to see this ??te,
    it consist? of precious Information.

    My weblog cheap Outdoor Carpet

  28. Wallace says:

    I’ll right away snatch your rss feed as I can’t to find your
    email subscription link or newsletter service.
    Do you have any? Kindly permit me recognise in order that I may subscribe.
    Thanks.

    Here is my site clash of clans hack no survey 2013

  29. Chara says:

    A motivating discussion is worth comment. I believe that you should write more about this subject, it may not be
    a taboo subject but generally people do not discuss these subjects.
    To the next! Kind regards!!

    Look into my site … decorah eagles

  30. Merry says:

    Good post. I am facing a few of these issues as well..

    Also visit my blog … slimmeryouin2

  31. Shannon says:

    Fantastic items from you, man. I’ve be mindful your stuff prior to and you are
    simply too fantastic. I really like what you’ve received here, really
    like what you’re saying and the best way during which you assert it.
    You make it enjoyable and you still take
    care of to stay it smart. I can not wait to read much more from
    you. This is really a wonderful website.

    Also visit my blog post; rugsusa.com (http://www.kaylaaimee.com)

  32. Charli says:

    A motivating discussion is worth comment. I think that you should publish more about this subject matter, it might not
    be a taboo matter but typically folks don’t speak about such issues.
    To the next! Cheers!!

    Feel free to surf to my webpage; Kingsroad Hack

  33. Isidra says:

    I’m gone to convey my little brother, that he should
    also visit this web site on regular basis to take updated from newest news update.

    my web page: home improvement

Leave a Reply