Comment intercepter des touches sur des objets MKMapView ou UIWebView?
Je ne suis pas sûr de ce que je fais mal mais j'essaie d'attraper des touches sur un objet MKMapView
. Je l'ai subdivisé en créant la classe suivante:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface MapViewWithTouches : MKMapView {
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event;
@end
et la mise en œuvre:
#import "MapViewWithTouches.h"
@implementation MapViewWithTouches
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event {
NSLog(@"hello");
//[super touchesBegan:touches withEvent:event];
}
@end
mais il semble que lorsque j'utilise cette classe, Je ne vois rien sur la Console:
MapViewWithTouches *mapView = [[MapViewWithTouches alloc] initWithFrame:self.view.frame];
[self.view insertSubview:mapView atIndex:0];
une idée de ce que je fais de mal?
14 réponses
le meilleur moyen que j'ai trouvé pour réaliser ceci est avec un reconnaisseur de geste. D'autres moyens s'avèrent impliquer beaucoup de programmation hackish qui reproduit imparfaitement le code D'Apple, en particulier dans le cas de multitouch.
voici ce que je fais: mettre en œuvre un reconnaisseur de geste qui ne peut être empêché et qui ne peut pas empêcher d'autres reconnaisseurs de geste. Ajoutez - le à la vue de carte, puis utilisez le touchesBegan de gestureRecognizer, touchesMoved, etc. votre fantaisie.
Comment détecter n'importe quel robinet à l'intérieur d'un MKMapView (sans astuces)
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
SWIFT 3
let tapInterceptor = WildCardGestureRecognizer(target: nil, action: nil)
tapInterceptor.touchesBeganCallback = {
_, _ in
self.lockedOnUserLocation = false
}
mapView.addGestureRecognizer(tapInterceptor)
WildCardGestureRecognizer.swift
import UIKit.UIGestureRecognizerSubclass
class WildCardGestureRecognizer: UIGestureRecognizer {
var touchesBeganCallback: ((Set<UITouch>, UIEvent) -> Void)?
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.cancelsTouchesInView = false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
touchesBeganCallback?(touches, event)
}
override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
après une journée de pizzas, de cris, j'ai enfin trouvé la solution! Très soigné!
Peter, j'ai utilisé votre astuce ci-dessus et l'ai légèrement modifié pour finalement avoir une solution qui fonctionne parfaitement avec MKMapView et devrait fonctionner aussi avec 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
j'espère que cela aidera certains d'entre vous!
Cheers
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleGesture:)];
tgr.numberOfTapsRequired = 2;
tgr.numberOfTouchesRequired = 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];
//.............
}
pour un MKMapView la vraie solution de travail est la reconnaissance gestuelle !
Moi je voulais mettre à jour le centre de la carte sur ma position quand je fais glisser la carte ou pincer pour zoomer.
ainsi, créer et ajouter votre reconnaisseur de geste à la 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];
}
Look UIGestureRecognizer Classe de Référence pour voir tous disponibles geste de reconnaissance.
parce que nous avons défini le délégué à soi, nous devons mettre en œuvre le protocole UIGestureRecognizerDelegate:
typedef enum {
MapModeStateFree, // Map is free
MapModeStateGeolocalised, // Map centred on our location
MapModeStateGeolocalisedWithHeading // Map centred on our location and oriented with the compass
} MapModeState;
@interface MapViewController : UIViewController <CLLocationManagerDelegate, UIGestureRecognizerDelegate> {
MapModeState mapMode;
}
@property (nonatomic, retain) IBOutlet MKMapView *mapView;
...
et outrepasser la méthode gestureRecognizer: gestureRecognizer shouldrecognizesimultaneously withgesturerecognizer: afin de permettre de reconnaître plusieurs gestes simultanément, si j'ai bien compris:
// Allow to recognize multiple gestures simultaneously (Implementation of the protocole UIGestureRecognizerDelegate)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Ecrivez maintenant les méthodes qui seront appelées par nos reconnaisseurs de geste:
// 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];
}
Juste au cas où quelqu'un essaie de faire comme moi: j'ai voulu créer une annotation au moment où l'utilisateur appuie. Pour cela j'ai utilisé la solution 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];
.......
}
cependant, didTapOnMap:
a aussi été appelé quand j'ai tapé sur l'annotation et un nouveau serait créé. La solution est de mettre en œuvre le UIGestureRecognizerDelegate
:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[MKAnnotationView class]])
{
return NO;
}
return YES;
}
vous aurez probablement besoin de superposer une vue transparente pour attraper les touches comme on le fait si souvent avec des contrôles basés sur UIWebView. La vue de carte fait déjà un tas de choses spéciales avec une touche afin de permettre à la carte d'être déplacée, centrée, zoomée, etc... que les messages ne seront pas envoyés à votre application.
deux autres options (non testées) auxquelles je peux penser:
1) démissionnez du premier intervenant par L'intermédiaire de L'IB et réglez-le sur " Propriétaire" pour permettre au propriétaire du fichier de répondre aux touches. Je doute que cela fonctionne parce que MKMapView étend NSObject, pas UIView et un résultat les événements touch peuvent encore ne pas se propager jusqu'à vous.
2) Si vous voulez piéger lorsque l'état de la carte change (comme sur un zoom) il suffit d'implémenter le protocole MKMapViewDelegate pour écouter des événements particuliers. Mon intuition est que c'est votre meilleure chance de piéger une certaine interaction facilement (à moins de mettre en œuvre le transparent Vue de dessus de la Carte). N'oubliez pas de configurer le contrôleur de vue logeant le MKMapView comme délégué de la carte ( map.delegate = self
).
Bonne Chance.
Je n'ai pas expérimenté, mais il y a de bonnes chances que MapKit soit basé sur un cluster de classe, et par conséquent subclassing il est difficile et inefficace.
je suggère de faire de la vue MapKit un sous-view d'une vue personnalisée, ce qui devrait vous permettre d'intercepter les événements tactiles avant qu'ils ne l'atteignent.
donc, après une demi-journée de tâtonnements, j'ai trouvé ce qui suit:
- comme tout le monde a trouvé, pincer ne fonctionne pas. J'ai essayé à la fois le subclassing MKMapView et la méthode décrite ci-dessus (l'intercepter). Et le résultat est le même.
-
dans les vidéos de Stanford iPhone, un gars D'Apple dit que beaucoup de choses UIKit sera cause beaucoup d'erreurs si vous "transférez" les requêtes touch (Alias les deux méthodes décrit ci-dessus), et vous ne le ferez probablement pas fonctionner.
-
LA SOLUTION : qui est décrit ici: Interception/Détournement iPhone Touch Événements pour MKMapView . Fondamentalement, vous "attrapez" l'événement avant que n'importe quel répondant l'obtient, et l'interprètent là.
faire de MKMapView un sous-vue d'une vue personnalisée et implémenter
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
dans la vue personnalisée pour retourner soi-même au lieu du subview.
Merci pour la pizza et les cris - vous m'avez fait gagner beaucoup de temps.
multipletouchenabled fonctionnera sporadiquement.
viewTouch.multipleTouchEnabled = TRUE;
à la fin, j'ai changé les vues lorsque j'avais besoin de capturer le toucher (autre point dans le temps que d'avoir besoin de pinceaux):
[mapView removeFromSuperview];
[viewTouch addSubview:mapView];
[self.view insertSubview:viewTouch atIndex:0];
je note que vous pouvez suivre le nombre et l'emplacement des touches, et obtenir l'emplacement de chaque dans une vue:
- (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];
}
est-ce que quelqu'un d'autre a essayé d'utiliser ces valeurs pour mettre à jour le niveau de zoom de la carte? Il s'agirait d'enregistrer les positions de départ, puis les positions d'arrivée, de calculer la différence relative et de mettre à jour la carte.
je joue avec le code de base fourni par Martin, et on dirait que ça va marcher...
voici ce que j'ai mis ensemble, qui ne permet zooms pincement dans le simulateur( n'ont pas essayé sur un vrai iPhone), mais je pense que serait très 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];
}
l'idée principale est que si l'utilisateur utilise deux doigts, vous suivez les valeurs. J'enregistre les points de départ et de fin dans les points de départ A et B. puis j'enregistre les points de suivi actuels, et quand j'ai fini, sur touchesEnded, je peux appeler une routine pour calculer les longueurs relatives de la ligne entre les points I commencer par, et la ligne entre le point que je termine avec l'utilisation simple hypoténuse calc. Le rapport entre eux est le nombre de zoom: je multiplie l'étendue de la région par ce nombre.
J'espère que c'est utile à quelqu'un.
j'ai pris l'idée d'une vue transparente de" superposition", de la réponse de MystikSpiral, et cela a fonctionné parfaitement pour ce que j'essayais d'atteindre; solution rapide et propre.
bref, j'avais une Viewcell (conçue en IB) personnalisable avec un MKMapView à gauche et quelques UILabels à droite. Je voulais créer la cellule personnalisée pour que vous puissiez la toucher n'importe où et cela pousserait un nouveau contrôleur de vue. Mais toucher à la carte n'a pas fait passer de touches "up" à la Uitable viewcell jusqu'à ce que j'ai simplement ajouté un uivi de la même taille que la vue de carte juste au-dessus de lui (en IB) et a fait son arrière-plan la "couleur claire" dans le code (ne pensez-vous pas que vous pouvez définir clearColor en IB??):
dummyView.backgroundColor = [UIColor clearColor];
pensait que cela pourrait aider quelqu'un d'autre; certainement si vous voulez obtenir le même comportement pour une table vue cellule.
Dans 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)
}
@objc 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)")
}
}