拦截/劫持iPhone触摸事件以用于MKMapView

6
在3.0 SDK中是否存在一个错误,禁用了MKMapView的实时缩放和拦截缩放手势?我有一些非常简单的代码可以检测点击事件,但是存在两个问题:
  1. 缩放手势总是被解释为缩小
  2. 所有缩放手势都不会实时更新地图视图。
在hitTest中,如果我返回“map”视图,则MKMapView功能正常,但我没有机会拦截事件。
有任何想法吗?
MyMapView.h:
@interface MyMapView : MKMapView
{
    UIView      *map;
}

MyMapView.m:

- (id)initWithFrame:(CGRect)frame
{
    if (![super initWithFrame:frame])
        return nil;

    self.multipleTouchEnabled = true;

    return self;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Hit Test");
    map = [super hitTest:point withEvent:event];
    return self;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s", __FUNCTION__);
    [map touchesCancelled:touches withEvent:event];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event
{
    NSLog(@"%s", __FUNCTION__);
    [map touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    NSLog(@"%s, %x", __FUNCTION__, mViewTouched);
    [map touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    NSLog(@"%s, %x", __FUNCTION__, mViewTouched);
    [map touchesEnded:touches withEvent:event];
}
6个回答

8
我发现最好的方法是使用手势识别器来实现。不清楚您是否想自己识别缩放事件,还是仅仅检测用户何时进行缩放/平移。
我不需要检测地图平移或缩放——我只关心用户是否在MKMapView的任何位置放下了手指。如果您需要100%准确地检测缩放或平移,可能会比这更复杂。我们真正需要的是MKMapView的开源实现,这样我们就可以将其添加到委托中,以及许多其他功能。
这是我的做法:实现一个无法被阻止且不能阻止其他手势识别器的手势识别器。将其添加到地图视图中,并根据需要处理相关触摸事件。
如何检测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

1
正是我所需要的,谢谢!你介意我在我的开源库中使用你的WildcardGestureRecognizer吗?该库旨在简化MapKit处理(目前处于初级阶段,但我计划逐步扩展它的功能)。https://github.com/myell0w/MTLocateMeBarButtonItem - myell0w
1
去吧。我将其释放到公共领域。 - gonzojive
这个应该检测平移/缩放事件吗?我无法将事件传递到我的MKMapView。 - lostintranslation

5
我有同样的问题 - 我想在地图视图上绘制比例尺。为了做到这一点,我必须拦截触摸事件,然后将它们发送回地图视图。不幸的是,当MKMapView不再是事件的原始接收者时,一些平滑的平移和缩放动画不再起作用。
然而,我已经找到了解决这个问题的方法 - 有点hacky但是行得通: 1.我把我的MapScales UIView放在MKMapView的顶部,并关闭它的事件接收,以便下面的MKMapView默认接收事件。 2.我用MyMainWindow类子类化UIWindow,并在其中重写了方法:
- (void) sendEvent:(UIEvent*)event {
  [super sendEvent:event];
  [self send_the_event_also_to_my_MapScales_component_with_use_of_listener_design_pattern];
}
  1. 我已经将我的应用程序的主窗口设置为MyMainWindow的实例。

这样,我的MapScales组件就可以接收并响应所有触摸事件,同时不会破坏底层的MKMapView :)


2
这不是黑客行为,而是解决问题的正确方式。组合而非继承。仅仅因为你可以从MKMapView继承,并不意味着你应该这样做。 - slf
当地图不是窗口中唯一的视图时,比如你有一个导航栏或其他东西,你要如何做到这一点呢?你不希望点击导航栏传递到MapScales。难道唯一的方法就是检查大小吗? - DevDevDev
另一种不那么硬编码的方式是使用以下代码:@property(nonatomic, assign) id tapDelegate; if (tapDelegate == nil) return; NSSet *touches = [event allTouches]; if( touches.count!=1 ) return; UITouch *touch = [touches anyObject]; if( touch.tapCount==2 && touch.phase == UITouchPhaseEnded ){ [tapDelegate touchesEnded:touch]; } - johndpope
在这里再加上一点代码会更好地解释事情。 - lostintranslation

2
#import <UIKit/UIKit.h>
#import "UIViewTouch.h"
#import <MapKit/MapKit.h>

@interface MapTouchAppDelegate : NSObject <UIApplicationDelegate>
{
    UIViewTouch *viewTouch;
    MKMapView *mapView;
    UIWindow *window;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) UIViewTouch *viewTouch;
@property (nonatomic, retain) MKMapView *mapView;

@end


#import "MapTouchAppDelegate.h"

@implementation MapTouchAppDelegate

@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];
    // Override point for customization after application launch
    [window makeKeyAndVisible];
}


- (void)dealloc {
    [window release];
    [super dealloc];
}

#import <UIKit/UIKit.h>


@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


#import "UIViewTouch.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");
    self.multipleTouchEnabled = true;
    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

这段代码将会检测触摸和缩放。


1
为了补充@chomasek的答案,在仅处理地图视图的触碰时,我执行以下操作:
  1. 给地图视图一个标签,比如99
  2. 当我获取到触碰时,遍历接收视图的视图层级,查找具有上述标签的视图。
以下是代码:
// this is in the view controller containing the map view
// kICTagForMapView is just a constant
_mapView.tag = kICTagForMapView;

然后在 sendEvent:

// this does into the UIWindow subclass
BOOL isMapView = NO;    
UIView* superView = touch.view.superview;
while(superView)
{
    //Debug(@"superView = %@", superView);
    if (superView.tag == kICTagForMapView)
    {
        isMapView = YES;
        break;
    }
    superView = superView.superview;
}

if (isMapView == NO) return;

// do stuff here

1

试试

[super touchesEnded:touches withEvent:event];

而不是

[map touchesEnded:touches withEvent:event];

并将这个想法应用到所有的触摸事件方法中。这样,触摸事件就应该能够沿着响应者链传递,和平将会恢复。


0

MKMapView不响应上述的触摸方法...


我不相信那个。通过将事件传递给地图视图,地图可以平移和缩放。但是,当“map”被替换为“super”时,地图就没有任何交互性了。整个MapKit框架在我看来感觉有点不完整。存在这个问题,应该在MapViewDelegate中添加一个回调方法以选择注释。 - Shawn
1
据我所知,一些苹果自定义UI(如UIScrollView、MKMapView)不允许您掌握触摸事件。我认为在MapViewDelegate中有一个回调方法用于选择注释... - Daniel
当然它可以响应触摸,但是除非你继承它或包装它,否则它不提供拦截触摸的方法。 - slf
我没有说它不响应触摸,我说它不响应上面列出的触摸方法,这意味着你不能拦截触摸... - Daniel
顺便说一下,从MKMapView继承也不能让你拦截触摸。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接