[英]How to intercept touches events on a MKMapView or UIWebView objects?
我不確定我做錯了什么,但我嘗試捕捉對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 的代碼,尤其是在多點觸控的情況下。
這就是我所做的:實現一個無法阻止且無法阻止其他手勢識別器的手勢識別器。 將其添加到地圖視圖中,然后根據您的喜好使用gestureRecognizer 的touchesBegan、touchesMoved 等。
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
self.lockedOnUserLocation = NO;
};
[mapView addGestureRecognizer:tapInterceptor];
//
// 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
// 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
let tapInterceptor = WildCardGestureRecognizer(target: nil, action: nil)
tapInterceptor.touchesBeganCallback = {
_, _ in
self.lockedOnUserLocation = false
}
mapView.addGestureRecognizer(tapInterceptor)
WildCardGestureRecognizer.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];
//.............
}
我想在拖動地圖或捏合縮放時停止更新我所在位置的地圖中心。
因此,創建您的手勢識別器並將其添加到 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 類參考以查看所有可用的手勢識別器。
因為我們已經定義了 self 的委托,所以我們必須實現協議 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 的控件經常做的那樣。 Map View 已經通過觸摸做了一些特殊的事情,以便允許移動、居中、縮放地圖等......消息不會冒泡到您的應用程序中。
我能想到的另外兩個(未經測試)選項:
1)通過IB退出第一響應者並將其設置為“文件的所有者”以允許文件的所有者響應觸摸。 我懷疑這會起作用,因為 MKMapView 擴展了 NSObject,而不是 UIView,因此觸摸事件仍然可能不會傳播給您。
2) 如果您想在 Map 狀態更改時(例如縮放時)進行捕獲,只需實現 MKMapViewDelegate 協議來偵聽特定事件。 我的預感是,這是您輕松捕獲某些交互的最佳方法(沒有在地圖上實現透明視圖)。 不要忘記將包含 MKMapView 的視圖控制器設置為地圖的委托( map.delegate = self
)。
祝你好運。
我還沒有嘗試過,但很有可能 MapKit 是基於類集群的,因此對其進行子類化既困難又無效。
我建議將 MapKit 視圖設為自定義視圖的子視圖,這樣您就可以在觸摸事件到達之前攔截它們。
所以在搞了半天之后,我發現了以下內容:
在斯坦福 iPhone 的視頻中,蘋果的一個人說,如果你“傳輸”觸摸請求(也就是上面描述的兩種方法),很多 UIKit 的東西會導致很多錯誤,你可能不會讓它工作。
解決方案:在此處描述: Intercepting/Hijacking iPhone Touch Events for MKMapView 。 基本上你在任何響應者得到它之前“捕捉”事件,並在那里解釋它。
在 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)")
}
}
使 MKMapView 成為自定義視圖的子視圖並實現
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
在自定義視圖中返回 self 而不是子視圖。
謝謝你的披薩和尖叫——你為我節省了很多時間。
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];
}
有沒有其他人嘗試使用這些值來更新地圖的縮放級別? 這將是記錄開始位置,然后是結束位置,計算相對差異並更新地圖的問題。
我正在使用 Martin 提供的基本代碼,這看起來會起作用......
這是我放在一起的內容,它確實允許在模擬器中進行縮放(尚未在真正的 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,直到我只是在它的頂部(在 IB 中)添加了一個與地圖視圖相同大小的 UIView 並使其背景成為代碼中的“清晰顏色”(不認為您可以在 IB 中設置 clearColor 嗎??):
dummyView.backgroundColor = [UIColor clearColor];
認為它可能會幫助別人; 當然,如果您想為表格視圖單元格實現相同的行為。
我不明白為什么其他答案如此復雜。 解決方案實際上只是一行:
mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(mapTapped)))
@objc func mapTapped() {}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.