본문 바로가기

iPhone dev./MKMapView, LocationManager

MKMapView에서 터치 인식하기 (stackoverflow Q&A)

Hello,

I'm not sure what i'm doing wrong but I try to catch touches on a MKMapView object. I subclassed it by creating the following class :

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@interface MapViewWithTouches : MKMapView {

}

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

@end

And the implementation :

#import "MapViewWithTouches.h"
@implementation MapViewWithTouches

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

   
NSLog(@"hello");
   
//[super touchesBegan:touches       withEvent:event];

}
@end

But it looks like when I use this class, I see nothing on the Console :

MapViewWithTouches *mapView = [[MapViewWithTouches alloc] initWithFrame:self.view.frame];
[self.view insertSubview:mapView atIndex:0];

Any idea what I'm doing wrong?

Heelp! :)

Thanks a lot!

Martin

link|edit|flag

15 Answers

up vote23down voteaccepted

After a day of pizzas, screamings, I finally found the solution! Very neat!

Peter, I used your trick above and tweaked it a little bit to finally have a solution which work perfectly with MKMapView and should work also with UIWebView

MKTouchAppDelegate.h

#import <UIKit/UIKit.h>
@class UIViewTouch;
@class MKMapView;

@interface MKTouchAppDelegate : NSObject <UIApplicationDelegate> {
   
UIWindow *window;
   
UIViewTouch *viewTouch;
   
MKMapView *mapView;
}
@property (nonatomic, retain) UIViewTouch *viewTouch;
@property (nonatomic, retain) MKMapView *mapView;
@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

MKTouchAppDelegate.m

#import "MKTouchAppDelegate.h"
#import "UIViewTouch.h"
#import <MapKit/MapKit.h>

@implementation MKTouchAppDelegate

@synthesize window;
@synthesize viewTouch;
@synthesize mapView;


- (void)applicationDidFinishLaunching:(UIApplication *)application {

   
//We create a view wich will catch Events as they occured and Log them in the Console
    viewTouch
= [[UIViewTouch alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];

   
//Next we create the MKMapView object, which will be added as a subview of viewTouch
    mapView
= [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
   
[viewTouch addSubview:mapView];

   
//And we display everything!
   
[window addSubview:viewTouch];
   
[window makeKeyAndVisible];


}


- (void)dealloc {
   
[window release];
   
[super dealloc];
}


@end

UIViewTouch.h

#import <UIKit/UIKit.h>
@class UIView;

@interface UIViewTouch : UIView {
   
UIView *viewTouched;
}
@property (nonatomic, retain) UIView * viewTouched;

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

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

@end

UIViewTouch.m

#import "UIViewTouch.h"
#import <MapKit/MapKit.h>

@implementation UIViewTouch
@synthesize viewTouched;

//The basic idea here is to intercept the view which is sent back as the firstresponder in hitTest.
//We keep it preciously in the property viewTouched and we return our view as the firstresponder.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
   
NSLog(@"Hit Test");
    viewTouched
= [super hitTest:point withEvent:event];
   
return self;
}

//Then, when an event is fired, we log this one and then send it back to the viewTouched we kept, and voilà!!! :)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   
NSLog(@"Touch Began");
   
[viewTouched touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   
NSLog(@"Touch Moved");
   
[viewTouched touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   
NSLog(@"Touch Ended");
   
[viewTouched touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
   
NSLog(@"Touch Cancelled");
}

@end

I hope that will help some of you!

Cheers

link|edit|flag
5  
Nice. Small suggestion: You should avoid naming your own classes with a UI prefix. Apple reserves/discourages using NS or UI as a class prefix, because these might end up clashing with an Apple class (even if it's a private class). – Daniel Dickison Jun 27 '09 at 15:44
Hey Daniel, You are perfectly right, I thought that too! To complete my answer above, let me add a little warning : My Example assume there's only one object viewTouched, which are consuming all the events. But that's not true. You could have some Annotations on top of your Map and then my code doesn't work anymore. To work 100%, you need to remember for each hitTest the view associated to that specific event (and eventually release it when touchesEnded or touchesCancelled is triggered so you don't need to keep track of finished events...). – Martin Jun 27 '09 at 16:39
1  
Very useful code, thanks Martin! I was wondering if you tried pinch-zooming the map after implementing this? For me when I got it working using basically the same code you have above everything seemed to work except pinch zooming the map. Anyone have any ideas? – Adam Alexander Jun 29 '09 at 18:03
Hey Adam, I also have this limitation and I don't really understand why! That really is annoying. If you find a solution, let me know! Thx – Martin Jul 1 '09 at 12:19
Ok, I voted this one up because it initially appeared to solve my problem. HOWEVER...! I can't seem to get multi-touch to work. That is, even though I directly pass touchesBegan and touchesMoved to viewTouched (I do my intercepting on touchesEnded), I cannot zoom the map with pinch gestures. (Continued...) – Olie Jul 1 '09 at 17:02
show 7 more comments

None of the previous solutions above will work perfectly, especially in cases involving multitouch. This solutions is the least intrusive, and from my experience the best.

The best way I have found to achieve this is with a Gesture Recognizer. Other ways turn out to involve a lot of hackish programming that imperfectly duplicates Apple's code.

Here's what I do: Implement a gesture recognizer that cannot be prevented and that cannot prevent other gesture recognizers. Add it to the map view, and then use the gestureRecognizer's touchesBegan, touchesMoved, etc. to your fancy.

How to detect any tap inside an MKMapView (sans tricks)

WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor
.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
        self
.lockedOnUserLocation = NO;
};
[mapView addGestureRecognizer:tapInterceptor];

WildcardGestureRecognizer.h

//
//  WildcardGestureRecognizer.h
//  Copyright 2010 Floatopian LLC. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef void (^TouchesEventBlock)(NSSet * touches, UIEvent * event);

@interface WildcardGestureRecognizer : UIGestureRecognizer {
   
TouchesEventBlock touchesBeganCallback;
}
@property(copy) TouchesEventBlock touchesBeganCallback;


@end

WildcardGestureRecognizer.m

//
//  WildcardGestureRecognizer.m
//  Created by Raymond Daly on 10/31/10.
//  Copyright 2010 Floatopian LLC. All rights reserved.
//

#import "WildcardGestureRecognizer.h"


@implementation WildcardGestureRecognizer
@synthesize touchesBeganCallback;

-(id) init{
   
if (self = [super init])
   
{
        self
.cancelsTouchesInView = NO;
   
}
   
return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
   
if (touchesBeganCallback)
        touchesBeganCallback
(touches, event);
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}

- (void)reset
{
}

- (void)ignoreTouch:(UITouch *)touch forEvent:(UIEvent *)event
{
}

- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
   
return NO;
}

- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
   
return NO;
}

@end
link|edit|flag
This solution worked perfectly for me! – GendoIkari Nov 17 '10 at 23:37
This worked great! – Gerald Kaszuba Jan 3 at 4:03
what's "lockedOnUserLocation" for? – Joe Jan 20 at 11:40
that's an extraneous variable specific to my application. it keeps track of whether the system should automatically center the map on the current location – gonzojive Jan 20 at 15:55
1  
This works great for the most part but I have found one problem with it. If the HTML in your web view contains an HTML5 video tag with controls, the gesture recognizer will prevent the user from being able to use the controls. I have been looking for a workaround for this but have yet to find one. – Bryan Irace Apr 1 at 13:41