Как перехватить события касаний на объектах MKMapView или UIWebView? - PullRequest
96 голосов
/ 26 июня 2009

Я не уверен, что делаю неправильно, но я пытаюсь уловить прикосновения к MKMapView объекту. Я вложил его в подкласс, создав следующий класс:

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

@interface MapViewWithTouches : MKMapView {

}

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

@end

И реализация:

#import "MapViewWithTouches.h"
@implementation MapViewWithTouches

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

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

}
@end

Но похоже, что когда я использую этот класс, я ничего не вижу на консоли:

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

Есть идеи, что я делаю не так?

Ответы [ 14 ]

145 голосов
/ 31 октября 2010

Лучший способ, который я нашел для достижения этой цели, - это распознаватель жестов. Другие способы включают в себя множество хакерских программ, которые несовершенно дублируют код Apple, особенно в случае мультитача.

Вот что я делаю: Реализация распознавателя жестов, который не может быть предотвращен и который не может предотвратить другие распознаватели жестов. Добавьте его в вид карты, а затем используйте прикосновения жеста-распознавателя Beg, touchesMoved и т. Д. По своему вкусу.

Как обнаружить любой сигнал внутри MKMapView (без хитростей)

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
    }
}
29 голосов
/ 27 июня 2009

После дня пиццы, криков я наконец нашел решение! Очень аккуратно!

Питер, я использовал твой трюк выше и немного подправил его, чтобы наконец-то найти решение, которое идеально работает с MKMapView и должно работать также с 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

Надеюсь, это поможет некоторым из вас!

Приветствия

23 голосов
/ 24 июня 2011
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];

    //.............
}
12 голосов
/ 20 апреля 2011

Для MKMapView реальным рабочим решением является распознавание жестов!

Мне Я хотел прекратить обновлять центр карты на моем месте, когда я перетаскивал карту или ущипнул, чтобы увеличить.

Итак, создайте и добавьте свой распознаватель жестов в 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];
}

Просмотрите справочник классов UIGestureRecognizer , чтобы увидеть все доступные средства распознавания жестов.

Поскольку мы определили делегата для себя, мы должны реализовать протокол 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;
...

И переопределить метод gestRecognizer: gestRecognizer долженRecognizeSim одновременноouslyWithGestureRecognizer: чтобы разрешить распознавать несколько жестов одновременно, если я правильно понял:

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

Теперь напишите методы, которые будут вызываться нашими распознавателями жестов:

// 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];
}
5 голосов
/ 29 марта 2015

На всякий случай, если кто-то пытается сделать то же самое, что и я: я хотел создать аннотацию в точке, где пользователь нажимает. Для этого я использовал решение 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];
    .......
}

Однако didTapOnMap: также вызывалось, когда я нажимал на аннотацию, и создавалась новая. Решение заключается в реализации UIGestureRecognizerDelegate:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([touch.view isKindOfClass:[MKAnnotationView class]])
    {
        return NO;
    }
    return YES;
}
3 голосов
/ 26 июня 2009

Вам, вероятно, потребуется наложить прозрачное представление, чтобы поймать прикосновения, как это часто делается с элементами управления на основе UIWebView. В представлении «Карта» уже выполняется одно касание, позволяющее перемещать, центрировать, масштабировать и т. Д. Карту, чтобы сообщения не попадали в ваше приложение.

Два других (НЕПРОВЕРЕННЫХ) варианта, которые я могу придумать:

1) Удалите первого респондента через IB и установите для него «Владелец файла», чтобы владелец файла мог реагировать на прикосновения. Я сомневаюсь, что это будет работать, потому что MKMapView расширяет NSObject, а не UIView, и в результате сенсорные события все еще могут не распространяться до вас.

2) Если вы хотите перехватывать данные при изменении состояния карты (например, при увеличении), просто реализуйте протокол MKMapViewDelegate для прослушивания определенных событий. Я догадываюсь, что это ваш лучший способ легко поймать какое-то взаимодействие (если не считать прозрачного просмотра карты). Не забудьте установить View Controller, содержащий MKMapView, в качестве делегата карты (map.delegate = self).

Удачи.

2 голосов
/ 03 июля 2017

In 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)")

    }

}
2 голосов
/ 26 июня 2009

Я не экспериментировал, но есть хороший шанс, что MapKit основан на кластере классов, и поэтому его создание подклассов сложно и неэффективно.

Я бы предложил сделать представление MapKit подпредставлением собственного представления, которое должно позволить вам перехватывать события касания до того, как они достигнут этого.

1 голос
/ 08 марта 2010

Итак, после полдня возни с этим я обнаружил следующее:

  1. Как и все остальные, защемление не работает. Я попробовал как подкласс MKMapView, так и метод, описанный выше (перехват). И результат тот же.
  2. В видео со Стэнфорда на iPhone парень из Apple говорит, что многие вещи из UIKit будут вызвать много ошибок, если вы «передаете» запросы касания (так называемые два метода, описанные выше), и вы, вероятно, не получите его на работу.

  3. РЕШЕНИЕ : описано здесь: Перехват / перехват событий касания iPhone для MKMapView . Обычно вы «ловите» событие до того, как его получит любой респондент, и интерпретируете его там.

0 голосов
/ 06 апреля 2010

Я взял идею «наложения» прозрачного представления из ответа MystikSpiral, и он отлично работал для того, чего я пытался достичь; быстрое и чистое решение.

Короче говоря, у меня был пользовательский UITableViewCell (разработанный в IB) с MKMapView слева и несколькими UILabels справа. Я хотел сделать пользовательскую ячейку, чтобы вы могли касаться ее где угодно, и это подтолкнуло бы новый контроллер представления. Однако касание карты не передавало касания «вверх» в UITableViewCell, пока я просто не добавил UIView того же размера, что и вид карты, прямо над ним (в IB) и не сделал его фон «прозрачным цветом» в коде ( не думаю, что вы можете установить clearColor в IB ??):

dummyView.backgroundColor = [UIColor clearColor];

Думал, что это может помочь кому-то еще; конечно, если вы хотите добиться того же поведения для ячейки табличного представления.

...