如何拦截MKMapView或UIWebView对象上的触摸事件?
我不知道我在做什么错,但我试图抓住一个MKMapView
对象的触摸。 我通过创build以下类来分类:
#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];
任何想法我做错了什么?
我发现实现这个最好的方法是使用手势识别器。 其他的方式会涉及到很多黑客程序,不完美地复制苹果的代码,特别是在多点触控的情况下。
这是我做的:实现一个手势识别器,不能被阻止,不能阻止其他手势识别器。 将它添加到地图视图,然后使用gestureRecognizer的touchesBegan,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
经过一天的比萨饼,尖叫,我终于find了解决办法! 井井有条!
彼得,我用你的技巧上面,并调整了一点点,终于有一个解决scheme,与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,真正的工作解决scheme是手势识别!
我想停止更新地图中心的位置,当我拖动地图或捏缩放。
所以,创build并添加你的手势识别器到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; ...
并重写方法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]; }
为了防止有人试图像我这样做:我想在用户点击的位置创build一个注释。 为此,我使用了UITapGestureRecognizer
解决scheme:
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:
也被称为当我点击注释和一个新的将被创build。 解决scheme是实现UIGestureRecognizerDelegate
:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if ([touch.view isKindOfClass:[MKAnnotationView class]]) { return NO; } return YES; }
你可能需要覆盖一个透明的视图来捕捉触摸,就像经常使用基于UIWebView的控件所做的那样。 地图视图已经做了一些特殊的事情,为了让地图被移动,居中,缩放等等,这些消息不会被冒泡到你的应用程序。
我能想到的另外两个(UNTESTED)选项:
1)通过IB辞退第一个响应者并将其设置为“文件所有者”以允许文件的所有者响应该触摸。 我可疑的是,这将工作,因为MKMapView扩展NSObject,而不是UIView结果触摸事件仍然可能不会传播给你。
2)如果你想陷阱时,地图状态变化(如在一个缩放)只是实现MKMapViewDelegate协议来监听特定的事件。 我的预感是,这是你在捕捉一些交互方面很容易的最佳select(缺less实现透明视图的方法)。 不要忘记将视图控制器设置为MKMapView作为地图的委托( map.delegate = self
)。
祝你好运。
我还没有尝试过,但MapKit有一个很好的机会基于一个类集群,因此对它进行子类化是困难和无效的。
我build议让MapKit视图成为自定义视图的子视图,这应该允许您在触摸事件到达之前拦截它。
所以经过半天的时间,我发现了以下几点:
- 正如其他人所发现的,捏是行不通的。 我尝试了MKMapView的子类和上面描述的方法(拦截它)。 结果是一样的。
-
在斯坦福的iPhonevideo中,一位来自苹果的人士表示,如果您“传输”触摸请求(又称为上述两种方法),许多UIKit的东西将导致大量错误,而且您可能无法使其工作。
-
解决scheme :在这里描述: 拦截/劫持MKMapView的iPhone触摸事件 。 基本上,在任何响应者得到它之前,你都要“捕捉”事件,并在那里解释它。
我从MystikSpiral的回答中获得了一个“叠加”的透明观点,它完美地为我想要实现的目标而努力。 快速,清洁的解决scheme。
简而言之,我有一个自定义的UITableViewCell(在IB中devise),左侧是MKMapView,右侧是一些UILabels。 我想制作自定义单元,以便在任何地方触摸它,这将推动一个新的视图控制器。 然而,触摸地图并没有通过“向上”触及UITableViewCell,直到我简单地在地图视图右上方添加了一个与地图视图相同大小的UIView(在IB中),并使其背景为代码中的“清除颜色”不要以为你可以在IB中设置clearColor ??):
dummyView.backgroundColor = [UIColor clearColor];
以为这可能会帮助别人; 当然,如果你想实现一个表视图单元格相同的行为。
使MKMapView成为自定义视图的子视图并实现
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
在自定义视图中返回自己而不是子视图。
感谢比萨和尖叫 – 你为我节省了很多时间。
多元化程序将偶尔工作。
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]; }
有没有其他人尝试使用这些值来更新地图的缩放级别? logging开始位置,然后logging结束位置,计算相对差值并更新地图。
我正在玩马丁提供的基本代码,这看起来像它会工作…
这里是我放在一起,这允许在模拟器捏缩放(还没有尝试过一个真正的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]; }
主要的想法是,如果用户使用两根手指,你跟踪值。 我loggingstartPoints A和B的起点和终点。然后我logging当前的跟踪点,当我完成时,在touchesEnded上,我可以调用一个例程来计算我开始点之间的线的相对长度,并且使用简单的斜边计算结束点I之间的界线。 它们之间的比例是放大量:我乘以该量的区域跨度。
希望对某人有用。
在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) } 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)") } }