Как перехватить события касаний объектов MKMapView или UIWebView?

StackOverflow https://stackoverflow.com/questions/1049889

Вопрос

Я не уверен, что делаю не так, но пытаюсь уловить прикосновения к 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];

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

Это было полезно?

Решение

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

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

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

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

WildcardGestureRecouncer.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

WildcardGestureRecouncer.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

СВИФТ 3

let tapInterceptor = WildCardGestureRecognizer(target: nil, action: nil)
tapInterceptor.touchesBeganCallback = {
    _, _ in
    self.lockedOnUserLocation = false
}
mapView.addGestureRecognizer(tapInterceptor)

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

Другие советы

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

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

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

Ваше здоровье

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

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

Для 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];
}

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

Поскольку мы определили делегата для себя, нам необходимо реализовать протокол UIGestureRecouncerDelegate:

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;
...

И переопределите методgestRecouncer:gestureRecouncer mustRecounceeSimulentiallyWithGestureRecouncer:чтобы разрешить распознавание нескольких жестов одновременно, если я правильно понял:

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

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

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

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

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

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

Удачи.

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

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

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

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

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

В Свифте 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)")

    }

}

Сделайте MKMapView подпредставлением пользовательского представления и реализуйте

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

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

Спасибо за пиццу и крики – вы сэкономили мне кучу времени.

Multipletouchenabled будет работать время от времени.

viewTouch.multipleTouchEnabled = TRUE;

В конце концов, я отключил виды, когда мне нужно было запечатлеть прикосновение (другой момент времени, чем при необходимости щипка):

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

Я заметил, что вы можете отслеживать количество и расположение касаний и получать местоположение каждого из них в представлении:

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

Кто-нибудь еще пытался использовать эти значения для обновления уровня масштабирования карты?Это будет вопрос регистрации стартовых позиций, а затем финишных позиций, расчета относительной разницы и обновления карты.

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

Вот что я собрал, что позволяет масштабировать масштаб в симуляторе (не пробовал на реальном iPhone), но думаю, все будет в порядке:

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

Основная идея заключается в том, что если пользователь использует два пальца, вы отслеживаете значения.Я записываю начальную и конечную точки в startPoints A и B.Затем я записываю текущие точки отслеживания, и когда я закончу, на touchesEnded я могу вызвать процедуру для расчета относительных длин линии между точками, с которых я начинаю, и линии между точкой, которую я заканчиваю, используя простую гипотенузу. расчетСоотношение между ними и есть величина масштабирования:Я умножаю диапазон региона на эту сумму.

Надеюсь, это кому-то пригодится.

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

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

dummyView.backgroundColor = [UIColor clearColor];

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top