97 votes

Comment intercepter les événements de touches sur un objet MKMapView ou UIWebView ?

Je ne sais pas ce que je fais de mal, mais j'essaie d'attraper des touches sur un MKMapView objet. Je l'ai sous-classé 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 mal ?

149voto

gonzojive Points 1498

Le meilleur moyen que j'ai trouvé pour y parvenir est d'utiliser un système de reconnaissance des gestes. Les autres moyens impliquent beaucoup de programmation pirate qui reproduit imparfaitement le code d'Apple, surtout dans le cas du multitouch.

Voici ce que je fais : Implémenter une reconnaissance de gestes qui ne peut pas être empêchée et qui ne peut pas empêcher d'autres reconnaissances de gestes. Ajoutez-le à la vue de la carte, puis utilisez les touchesBegan, touchesMoved, etc. du gestureRecognizer à votre guise.

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
    }
}

3 votes

À quoi sert "lockedOnUserLocation" ?

0 votes

Il s'agit d'une variable étrangère spécifique à mon application. Elle permet de savoir si le système doit centrer automatiquement la carte sur l'emplacement actuel.

0 votes

Cette solution est parfaite. J'ai besoin d'une précision : Dans la méthode "- (void)touchesBegan :(NSSet *)touches withEvent :(UIEvent *)event", à quoi sert d'utiliser le code : if (touchesBeganCallback) touchesBeganCallback(touches, event) ;

29voto

Martin Points 1128

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 un peu modifiée pour finalement avoir une solution qui fonctionne parfaitement avec MKMapView et devrait fonctionner également 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

14 votes

Nice. Petite suggestion : Vous devriez éviter de nommer vos propres classes avec un préfixe UI. Apple réserve/décourage l'utilisation de NS ou UI comme préfixe de classe, car ceux-ci pourraient finir par entrer en conflit avec une classe Apple (même si c'est une classe privée).

0 votes

Hé Daniel, tu as parfaitement raison, c'est ce que je pensais aussi ! Pour compléter ma réponse ci-dessus, laissez-moi ajouter un petit avertissement : Mon exemple suppose qu'il n'y a qu'un seul objet viewTouched, qui consomme tous les événements. Mais ce n'est pas vrai. Vous pouvez avoir des annotations au dessus de votre carte et alors mon code ne fonctionne plus. Pour fonctionner à 100%, vous devez retenir pour chaque hitTest la vue associée à cet événement spécifique (et éventuellement la libérer lorsque touchesEnded ou touchesCancelled est déclenché afin que vous n'ayez pas besoin de garder la trace des événements terminés...).

1 votes

Code très utile, merci Martin ! Je me demandais si tu avais essayé de faire un zoom par pincement sur la carte après avoir implémenté ce code ? Pour moi, lorsque j'ai réussi à le faire fonctionner en utilisant essentiellement le même code que vous avez ci-dessus, tout semblait fonctionner sauf le zoom par pincement de la carte. Quelqu'un a t-il une idée ?

24voto

Govind P N Points 1016
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];

    //.............
}

3 votes

Je ne sais pas pourquoi ce n'est pas la meilleure réponse. Elle semble fonctionner parfaitement et est beaucoup plus simple.

12voto

Joan Points 82

Pour un MKMapView, la vraie solution fonctionnelle est la reconnaissance des gestes !

Moi je voulais arrêter de mettre à jour le centre de la carte sur ma position lorsque je fais glisser la carte ou que je pince pour zoomer.

Donc, créez et ajoutez votre reconnaissance de gestes à 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];
}

Regardez le Référence de classe UIGestureRecognizer pour voir toutes les reconnaissances de gestes disponibles.

Comme nous avons défini le délégué à self, nous devons implémenter 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 surcharger la methode gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer : afin de permettre la reconnaissance de 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;
}

Maintenant, écrivez les méthodes qui seront appelées par nos reconnaisseurs de gestes :

// 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];
}

0 votes

Cette solution est parfaite ! Quelques petites choses ici : Si vous voulez intercepter quand l'utilisateur termine de faire n'importe quelle action en utilisant ceci devrait être suffisant - (void)handleLongPressAndPinchGesture :(UIGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateEnded) { NSLog(@"handleLongPressAndPinchGesture Ended") ; } }. }

0 votes

N'oubliez pas non plus d'ajouter le délégué <UIGestureRecognizerDelegate>.

2voto

grahamparks Points 10187

Je n'ai pas expérimenté, mais il y a de fortes chances que MapKit soit basé autour d'un cluster de classes, et donc que le sous-classer soit difficile et inefficace.

Je suggère de faire de la vue MapKit une sous-vue d'une vue personnalisée, ce qui devrait vous permettre d'intercepter les événements tactiles avant qu'ils ne l'atteignent.

0 votes

Bonjour Graham ! Merci pour votre aide ! Si je crée une vue super personnalisée comme vous le suggérez, comment pourrais-je ensuite transmettre les événements au MKMapView ? Une idée ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X