Как перехватить события касания на объектах 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];


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

652   14  

14 ответов:

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

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

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

//
//  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.Свифт

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

#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.м

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

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

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

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

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

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

Удачи.

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

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

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

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

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

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

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

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

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

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

viewTouch.multipleTouchEnabled = TRUE;

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

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

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

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

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

dummyView.backgroundColor = [UIColor clearColor];

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

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

    }

}

Comments

    Ничего не найдено.