Programming

MKMapView 또는 UIWebView 개체에서 터치 이벤트를 가로채는 방법은 무엇입니까?

procodes 2020. 8. 24. 21:17
반응형

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

내가 뭘 잘못하고 있는지 아십니까?


이를 달성하는 가장 좋은 방법은 제스처 인식기를 사용하는 것입니다. 다른 방법은 특히 멀티 터치의 경우 Apple의 코드를 불완전하게 복제하는 많은 해킹 프로그래밍을 포함하는 것으로 밝혀졌습니다.

내가하는 일은 다음과 같습니다. 방지 할 수없고 다른 제스처 인식기를 방지 할 수없는 제스처 인식기를 구현합니다. 지도보기에 추가 한 다음 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

SWIFT 3

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

피자와 비명을 지르며 하루를 보낸 후 마침내 해결책을 찾았습니다! 매우 깔끔합니다!

Peter, 위의 트릭을 사용하고 마침내 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의 경우 실제 작업 솔루션은 제스처 인식입니다!

나지도를 끌거나 손가락을 모아 확대 / 축소 할 때 내 위치의지도 중심 업데이트를 중지하고 싶었습니다.

따라서 제스처 인식기를 만들고 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 기반 컨트롤로 자주 수행되는 것처럼 터치를 포착하려면 투명 뷰를 오버레이해야 할 것입니다. 지도보기는 메시지가 앱에 표시되지 않는지도를 이동, 중앙, 확대 / 축소 할 수 있도록하기 위해 이미 터치로 여러 가지 특별한 작업을 수행합니다.

생각할 수있는 다른 두 가지 (UNTESTED) 옵션 :

1) IB를 통해 첫 번째 응답자를 사임하고 파일 소유자가 터치에 응답 할 수 있도록 "파일 소유자"로 설정합니다. MKMapView가 UIView가 아닌 ​​NSObject를 확장하기 때문에 이것이 작동 할 것이라고 의심 스럽습니다. 그 결과 터치 이벤트가 여전히 당신에게 전파되지 않을 수 있습니다.

2) 맵 상태가 변경 될 때 (예 : 확대 / 축소에서) 트랩하려면 MKMapViewDelegate 프로토콜을 구현하여 특정 이벤트를 수신하십시오. 내 직감은 이것이 상호 작용을 쉽게 포착하는 최선의 방법입니다 (지도 위에 투명한 뷰를 구현하는 것보다 부족함). MKMapView를 포함하는 View Controller를 맵의 델리게이트 ( map.delegate = self) 로 설정하는 것을 잊지 마십시오 .

행운을 빕니다.


나는 실험하지 않았지만 MapKit이 클래스 클러스터를 기반으로 할 가능성이 있으므로 하위 클래스를 만드는 것이 어렵고 비효율적입니다.

터치 이벤트가 도달하기 전에 가로 챌 수 있도록 MapKit보기를 사용자 정의보기의 하위보기로 만드는 것이 좋습니다.


그래서 반나절 동안 이것을 엉망으로 만든 후 다음을 발견했습니다.

  1. 다른 사람들이 발견했듯이 꼬집음은 작동하지 않습니다. MKMapView를 서브 클래 싱하고 위에서 설명한 방법을 모두 시도했습니다 (차단). 결과는 동일합니다.
  2. Stanford iPhone 비디오에서 Apple의 한 사람은 터치 요청 (위에서 설명한 두 가지 방법이라고도 함)을 "전송"하면 UIKit의 많은 것들이 많은 오류를 일으키고 아마 작동하지 않을 것이라고 말합니다.

  3. 해결책 : 여기에 설명되어 있습니다 : MKMapView에 대한 iPhone 터치 이벤트 가로 채기 / 하이재킹 . 기본적으로 응답자가 이벤트를 받기 전에 이벤트를 "잡아"거기서 해석합니다.


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

주요 아이디어는 사용자가 두 손가락을 사용하는 경우 값을 추적한다는 것입니다. 시작점 A와 B에 시작점과 끝점을 기록합니다. 그런 다음 현재 추적 점을 기록하고 완료되면 touchesEnded에서 루틴을 호출하여 시작점 사이의 선의 상대적 길이를 계산할 수 있습니다. , 그리고 간단한 빗변 계산을 사용하여 끝나는 점 사이의 선. 그들 사이의 비율은 확대 / 축소 정도입니다. 영역 범위에 그 정도를 곱합니다.

누군가에게 유용하기를 바랍니다.


MystikSpiral의 답변에서 "오버레이"투명 뷰라는 아이디어를 가져 왔고 제가 달성하려는 작업에 완벽하게 작동했습니다. 빠르고 깨끗한 솔루션.

요컨대, 왼쪽에 MKMapView가 있고 오른쪽에 일부 UILabels가있는 사용자 지정 UITableViewCell (IB에서 설계됨)이 있습니다. 사용자 지정 셀을 만들고 싶었으므로 어디에서나 터치하면 새로운 뷰 컨트롤러가 푸시됩니다. 그러나지도를 터치해도 바로 위에지도보기와 동일한 크기의 UIView를 추가하고 (IB에서) 코드에서 배경을 '선명한 색상'으로 만들기 전까지는 UITableViewCell에 터치가 '위로'전달되지 않았습니다. IB에서 clearColor를 설정할 수 있다고 생각하지 않습니까 ??) :

dummyView.backgroundColor = [UIColor clearColor];

다른 사람에게 도움이 될 것이라고 생각했습니다. 확실히 테이블 뷰 셀에 대해 동일한 동작을 달성하려는 경우.

참고 URL : https://stackoverflow.com/questions/1049889/how-to-intercept-touches-events-on-a-mkmapview-or-uiwebview-objects

반응형