Как перехватывать события касания объектов MKMapView или UIWebView? - программирование
Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

Как обнаружить любой щелчок внутри 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

Ответ 2

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

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

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

Приветствия

Ответ 3

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

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

Ответ 4

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

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

Итак, создайте и добавьте свой распознаватель жестов в 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 Class Reference, чтобы увидеть все доступные распознаватели жестов.

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

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

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

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

Ответ 6

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

Два других (UNTESTED) варианта, о которых я могу думать:

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

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

Удачи.

Ответ 7

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

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

Ответ 8

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

  • Как и все остальные, сжимание не работает. Я попробовал оба подкласса MKMapView и описанный выше метод (перехват). И результат тот же.
  • В iPhone от Stanford iPhone парень из Apple говорит, что многие из вещей UIKit будут вызывают много ошибок, если вы "передаете" сенсорные запросы (так называемые два метода, описанные выше), и вы, вероятно, не получите его работу.

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

Ответ 9

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

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

dummyView.backgroundColor = [UIColor clearColor];

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

Ответ 10

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

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

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

Ответ 11

Спасибо за пиццу и крики - вы спасли меня много времени.

multipletouchenabled будет работать спорадически.

viewTouch.multipleTouchEnabled = TRUE;

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

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

Ответ 12

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

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

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

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

Ответ 13

Вот то, что я собрал вместе, что позволяет масштабировать масштабирование в симуляторе (не пробовал на реальном 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. Затем я записываю текущие точки отслеживания, и когда я закончил, на touchEnded, я могу вызвать процедуру для вычисления относительных длин линии между точками, которые я начинаю с, а линия между точкой я заканчивается использованием простой гипотенузы calc. Соотношение между ними - это масштаб: я умножаю диапазон области на эту величину.

Надеюсь, это полезно кому-то.

Ответ 14

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

    }

}