¿Cómo interceptar toca events en un object MKMapView o UIWebView?

No estoy seguro de qué estoy haciendo mal, pero bash MKMapView en un object MKMapView . Lo subclasé creando la siguiente class:

 #import <UIKit/UIKit.h> #import <MapKit/MapKit.h> @interface MapViewWithTouches : MKMapView { } - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event; @end 

Y la implementación:

 #import "MapViewWithTouches.h" @implementation MapViewWithTouches - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event { NSLog(@"hello"); //[super touchesBegan:touches withEvent:event]; } @end 

Pero parece que cuando uso esta class, no veo nada en la console:

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

¿Alguna idea de lo que estoy haciendo mal?

La mejor manera que he encontrado para lograr esto es con un Gesture Recognizer. Otras forms implican una gran cantidad de progtwigción hackean que duplican imperfectamente el código de Apple, especialmente en el caso de multitouch.

Esto es lo que hago: implementar un reconocimiento de gestos que no se puede prevenir y que no puede evitar otros reconocedores de gestos. Agregalo a la vista de map y luego usa los toques de gestionRecognizerBegan, tocaMovido, etc. a tu gusto.

Cómo detectar cualquier toque dentro de un MKMapView (sin trucos)

 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 

Después de un día de pizzas, gritos, ¡finalmente encontré la solución! ¡Muy aseado!

Peter, utilicé tu truco arriba y pellizqué un poco para finalmente tener una solución que funcione perfectamente con MKMapView y debería funcionar también con 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 occunetworking 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 finetworking, 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 

¡Espero que eso ayude a algunos de ustedes!

Aclamaciones

 UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesture:)]; tgr.numberOfTapsRequinetworking = 2; tgr.numberOfTouchesRequinetworking = 1; [mapView addGestureRecognizer:tgr]; [tgr release]; - (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state != UIGestureRecognizerStateEnded) return; CGPoint touchPoint = [gestureRecognizer locationInView:mapView]; CLLocationCoordinate2D touchMapCoordinate = [mapView convertPoint:touchPoint toCoordinateFromView:mapView]; //............. } 

¡Para un MKMapView, la solución de trabajo real es con reconocimiento de gestos!

Yo quería dejar de actualizar el centro del map en mi location cuando arrastro el map o pellizco para hacer zoom.

Por lo tanto, cree y agregue su reconocedor de gestos a mapView:

 - (void)viewDidLoad { ... // Add gesture recognizer for map hoding UILongPressGestureRecognizer *longPressGesture = [[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressAndPinchGesture:)] autorelease]; longPressGesture.delegate = self; longPressGesture.minimumPressDuration = 0; // In order to detect the map touching directly (Default was 0.5) [self.mapView addGestureRecognizer:longPressGesture]; // Add gesture recognizer for map pinching UIPinchGestureRecognizer *pinchGesture = [[[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressAndPinchGesture:)] autorelease]; pinchGesture.delegate = self; [self.mapView addGestureRecognizer:pinchGesture]; // Add gesture recognizer for map dragging UIPanGestureRecognizer *panGesture = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)] autorelease]; panGesture.delegate = self; panGesture.maximumNumberOfTouches = 1; // In order to discard dragging when pinching [self.mapView addGestureRecognizer:panGesture]; } 

Mire la reference de class UIGestureRecognizer para ver todos los reconocedores de gestos disponibles.

Debido a que hemos definido el delegado en nosotros mismos, tenemos que implementar el protocole UIGestureRecognizerDelegate:

 typedef enum { MapModeStateFree, // Map is free MapModeStateGeolocalised, // Map centnetworking on our location MapModeStateGeolocalisedWithHeading // Map centnetworking on our location and oriented with the compass } MapModeState; @interface MapViewController : UIViewController <CLLocationManagerDelegate, UIGestureRecognizerDelegate> { MapModeState mapMode; } @property (nonatomic, retain) IBOutlet MKMapView *mapView; ... 

Y anule el gesto de metodo Recognizer: gestureRecognizer debería reconocer de manera simultánea con GestureRecognizer: para permitir reconocer múltiples gestos simultáneamente, si entendiera bien:

 // Allow to recognize multiple gestures simultaneously (Implementation of the protocole UIGestureRecognizerDelegate) - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } 

Ahora escribe los methods que serán llamados por nuestros reconocedores de gestos:

 // On map holding or pinching pause localise and heading - (void)handleLongPressAndPinchGesture:(UIGestureRecognizer *)sender { // Stop to localise and/or heading if (sender.state == UIGestureRecognizerStateBegan && mapMode != MapModeStateFree) { [locationManager stopUpdatingLocation]; if (mapMode == MapModeStateGeolocalisedWithHeading) [locationManager stopUpdatingHeading]; } // Restart to localise and/or heading if (sender.state == UIGestureRecognizerStateEnded && mapMode != MapModeStateFree) { [locationManager startUpdatingLocation]; if (mapMode == MapModeStateGeolocalisedWithHeading) [locationManager startUpdatingHeading]; } } // On dragging gesture put map in free mode - (void)handlePanGesture:(UIGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan && mapMode != MapModeStateFree) [self setMapInFreeModePushedBy:sender]; } 

Por si acaso alguien está tratando de hacer lo mismo como yo: quería crear una anotación en el punto donde el usuario toca. Para eso utilicé la solución UITapGestureRecognizer :

 UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapOnMap:)]; [self.mapView addGestureRecognizer:tapGestureRecognizer]; [tapGestureRecognizer setDelegate:self]; - (void)didTapOnMap:(UITapGestureRecognizer *)gestureRecognizer { CGPoint point = [gestureRecognizer locationInView:self.mapView]; CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; ....... } 

Sin embargo, didTapOnMap: también se llamó cuando hice tapping en la anotación y se crearía una nueva. La solución es implementar UIGestureRecognizerDelegate :

 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if ([touch.view isKindOfClass:[MKAnnotationView class]]) { return NO; } return YES; } 

Probablemente necesitará superponer una vista transparente para captar los toques, tal como se hace tan a menudo con los controles basados ​​en UIWebView. La vista de map ya hace un montón de cosas especiales con un toque para permitir que el map se mueva, centre, amplíe, etc., que los posts no se borren hasta su aplicación.

Otras dos opciones (INESPERADAS) en las que puedo pensar:

1) Cancele la primera respuesta a través de IB y configúrela en "Propietario del file" para permitir que el propietario del file responda los toques. Tengo dudas de que esto funcione porque MKMapView extiende NSObject, no UIView y como resultado, los events táctiles aún no se pueden propagar.

2) Si desea atrapar cuando cambia el estado del Mapa (por ejemplo, en un zoom) simplemente implemente el protocolo MKMapViewDelegate para escuchar events particulares. Mi corazon es que es tu mejor oportunidad para atrapar una interacción fácilmente (a exception de implementar la vista transparente sobre el map). No olvide configurar el controller de vista que aloja MKMapView como el delegado del map ( map.delegate = self ).

Buena suerte.

No he experimentado, pero hay muchas posibilidades de que MapKit se base en un cluster de classs y, por lo tanto, subclasificarlo es difícil e ineficaz.

Sugiero que la vista MapKit sea una subvista de una vista personalizada, que debería permitirle interceptar events táctiles antes de que lleguen a él.

Entonces, después de medio día de jugar con esto, encontré lo siguiente:

  1. Como todos los demás encontraron, pellizcar no funciona. Probé subclassando MKMapView y el método descrito anteriormente (interceptándolo). Y el resultado es el mismo.
  2. En los videos de iPhone de Stanford, un chico de Apple dice que muchas de las cosas de UIKit causarán muchos errores si "transfieres" las requestes de toque (también conocido como los dos methods descritos anteriormente) y probablemente no lo pongas a funcionar.

  3. LA SOLUCIÓN : se describe aquí: Interceptar / secuestrar events iPhone Touch para MKMapView . Básicamente, "capturas" el evento antes de que cualquier respondedor lo reciba e interprete allí.

Tomé la idea de una vista transparente de "superposition", de la respuesta de MystikSpiral, y funcionó perfectamente para lo que intentaba lograr; solución rápida y limpia.

En resumen, tenía un UITableViewCell personalizado (diseñado en IB) con un MKMapView en el lado izquierdo y algunos UILabels en el derecho. Quería hacer la celda personalizada para poder tocarla en cualquier lugar y esto empujaría un nuevo controller de vista. Sin embargo, al tocar el map no pasó "arriba" a la UITableViewCell hasta que simplemente agregué una UIView del mismo tamaño que la vista del map justo encima de ella (en IB) e hizo su background el "color claro" en el código ( ¿No crees que puedes configurar clearColor en IB ??):

 dummyView.backgroundColor = [UIColor clearColor]; 

Pensé que podría ayudar a alguien más; sin duda, si desea lograr el mismo comportamiento para una celda de vista de tabla.

Haga que el MKMapView sea una subvista de una vista personalizada e implementar

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

en la vista personalizada para volverse auto en lugar de la subvista.

Gracias por la pizza y los gritos, me has ahorrado mucho time.

multipletouchenabled funcionará esporádicamente.

 viewTouch.multipleTouchEnabled = TRUE; 

Al final, cambié las vistas cuando necesitaba capturar el toque (punto diferente en el time que necesitar pinchzooms):

  [mapView removeFromSuperview]; [viewTouch addSubview:mapView]; [self.view insertSubview:viewTouch atIndex:0]; 

Noto que puedes rastrear el número y la location de los toques, y get la location de cada uno en una vista:

 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"Touch Moved %d", [[event allTouches] count]); NSEnumerator *enumerator = [touches objectEnumerator]; id value; while ((value = [enumerator nextObject])) { NSLog(@"touch description %f", [value locationInView:mapView].x); } [viewTouched touchesMoved:touches withEvent:event]; } 

¿Alguien más ha intentado usar estos valores para actualizar el nivel de zoom del map? Sería cuestión de registrar las posiciones iniciales, y luego las ubicaciones finales, calcular la diferencia relativa y actualizar el map.

Estoy jugando con el código básico proporcionado por Martin, y parece que funcionará …

Esto es lo que puse juntos, que permite hacer pellizcos en el simulador (no he probado en un iPhone real), pero creo que estaría bien:

 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"Touch Began %d", [touches count]); reportTrackingPoints = NO; startTrackingPoints = YES; [viewTouched touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if ([[event allTouches] count] == 2) { reportTrackingPoints = YES; if (startTrackingPoints == YES) { BOOL setA = NO; NSEnumerator *enumerator = [[event allTouches] objectEnumerator]; id value; while ((value = [enumerator nextObject])) { if (! setA) { startPointA = [value locationInView:mapView]; setA = YES; } else { startPointB = [value locationInView:mapView]; } } startTrackingPoints = NO; } else { BOOL setA = NO; NSEnumerator *enumerator = [[event allTouches] objectEnumerator]; id value; while ((value = [enumerator nextObject])) { if (! setA) { endPointA = [value locationInView:mapView]; setA = YES; } else { endPointB = [value locationInView:mapView]; } } } } //NSLog(@"Touch Moved %d", [[event allTouches] count]); [viewTouched touchesMoved:touches withEvent:event]; } - (void) updateMapFromTrackingPoints { float startLenA = (startPointA.x - startPointB.x); float startLenB = (startPointA.y - startPointB.y); float len1 = sqrt((startLenA * startLenA) + (startLenB * startLenB)); float endLenA = (endPointA.x - endPointB.x); float endLenB = (endPointA.y - endPointB.y); float len2 = sqrt((endLenA * endLenA) + (endLenB * endLenB)); MKCoordinateRegion region = mapView.region; region.span.latitudeDelta = region.span.latitudeDelta * len1/len2; region.span.longitudeDelta = region.span.longitudeDelta * len1/len2; [mapView setRegion:region animated:YES]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (reportTrackingPoints) { [self updateMapFromTrackingPoints]; reportTrackingPoints = NO; } [viewTouched touchesEnded:touches withEvent:event]; } 

La idea principal es que si el usuario usa dos dedos, rastreará los valores. Grabo los puntos de inicio y finalización en startPoints A y B. Luego grabo los puntos de seguimiento actuales, y cuando termino, en touchesEnded, puedo llamar a una rutina para calcular las longitudes relativas de la línea entre los puntos que comienzo con , y la línea entre el punto I termina con el uso de calcografía de hipotenusa simple. La relación entre ellos es la cantidad de zoom: multiplico el range de región en esa cantidad.

Espero que sea útil para alguien.

En Swift 3.0

 import UIKit import MapKit class CoordinatesPickerViewController: UIViewController { @IBOutlet var mapView: MKMapView! override func viewDidLoad() { super.viewDidLoad() let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(clickOnMap)) mapView.addGestureRecognizer(tapGestureRecognizer) } func clickOnMap(_ sender: UITapGestureRecognizer) { if sender.state != UIGestureRecognizerState.ended { return } let touchLocation = sender.location(in: mapView) let locationCoordinate = mapView.convert(touchLocation, toCoordinateFrom: mapView) print("Tapped at lat: \(locationCoordinate.latitude) long: \(locationCoordinate.longitude)") } }