Multiple UITapGestureRecognizer not working on UIScrollView - ios

I want to add multiple UITapGestureRecognizer on UIScrollView but it recognise only one gesture.
I want to add first gesture for touch begin and second one for touch end event.
Following is my code:-
self.tapStartGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
self.tapStartGesture.numberOfTapsRequired = 1;
self.tapStartGesture.numberOfTouchesRequired = 1;
[self.tapStartGesture setState:UIGestureRecognizerStateBegan];
[self.scrollView addGestureRecognizer:self.tapStartGesture];
self.tapEndGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
self.tapEndGesture.numberOfTapsRequired = 1;
self.tapEndGesture.numberOfTouchesRequired = 1;
[self.scrollView addGestureRecognizer:self.tapEndGesture];
- (void)tapGesture:(UITapGestureRecognizer *)sender {
if(sender==self.tapStartGesture) {
NSLog(#"tapStartGesture");
} else if(sender==self.tapEndGesture) {
NSLog(#"tapEndGesture");
}
}

A tap gesture only has one state - "ended". You can't detect when a tap starts using a tap gesture. As you've seen, attempting to use two tap gestures doesn't accomplish what you want.
You need to implement the UIResponder methods touchesBegan and touchesEnded.
You may also want to see UITapGestureRecognizer - make it work on touch down, not touch up?
.

Issue solved by implement custom gesture.
File:-MyGesture.h
#import <UIKit/UIKit.h>
#interface MyGesture : UIGestureRecognizer
#end
File:-MyGesture.m
#import "MyGesture.h"
#implementation MyGesture
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (self.state == UIGestureRecognizerStatePossible) {;
self.state = UIGestureRecognizerStateBegan;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
self.state = UIGestureRecognizerStateEnded;
}
#end
How to Use:-
MyGesture *gesture = [[MyGesture alloc] initWithTarget:self action:#selector(myGesture:)];
[self.scrollView addGestureRecognizer:gesture];
- (void)myGesture:(MyGesture *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
NSLog(#"tapStartGesture");
} else if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"tapEndGesture");
}
}

Related

objective c UIImage click event is not firing

my image is not click table
-(void)makeBlockAction{
blocksArr = [NSMutableArray new];
}
and my function for event is
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch* myTouch = [[touches allObjects] objectAtIndex:0];
NSLog(#"test2");
if ( [ blocksArr containsObject: myTouch.view ])
{
myTouch.view.alpha = 0;
NSLog(#"test");
}
}
There are better ways to go about this, try adding a UITapGestureRecognizer to the UIImageView. That way you won't need to use the touchesEnded function and check all the touches.
Also, make sure you have set userInteractionEnabled on your UIImageView to YES and that your UIImageView is not underneath any other subviews that may be preventing the tap being detected.
EXAMPLE
- (void)viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(imageViewTapped:)];
[self.myImageView addGestureRecognizer:tapGestureRecognizer];
self.myImageView.userInteractionEnabled = YES;
[self.view bringSubviewToFront: self.myImageView];
}
- (void)imageViewTapped:(UITapGestureRecognizer *)tapGestureRecognizer
{
//Do what you need to do.
}

IOS/Objective-C: Detect swipe over UIVIew and button touch up in same location of screen

Hi I have some buttons that trigger action methods.
In addition, I would like the user to be able to swipe left or right over the area with the buttons to show some different buttons
My idea was to add a UIView with a clear background color on top of the buttons so that the user could still see the buttons but could also swipe to the right or left.
However, when the view is over the buttons the buttons no longer work and when it is under the buttons, the swipe is not detected.
In addition to changing the view color to clear, I also tried adjusting the alpha however this seems to work the same way. When alpha of the UIView is zero, swipes are no longer detected and when alpha is greater than zero, the buttons no longer work.
Can anyone suggest a way to do this?
Thanks in advance for any suggestions.
Code I am using for buttons and to detect swipes seems standard. Trying to figure out a way to expose view and buttons at the same time.
Action methods to which buttons are wired:
//.h file
- (IBAction)showIncoming:(id)sender;
- (IBAction)showOutcoming:(id)sender;
/.m file
- (IBAction)showIncoming:(id)sender {
_showIncoming=YES;
_fetchedResultsController=nil;
[self.tableView reloadData];
[self updateInterface];
}
- (IBAction)showOutgoing:(id)sender {
_showIncoming=NO;
_fetchedResultsController=nil;
[self.tableView reloadData];
[self updateInterface];
}
Swipes:
//in viewWillAppear
UISwipeGestureRecognizer *rightRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(rightSwipeHandle:)];
rightRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[rightRecognizer setNumberOfTouchesRequired:1];
rightRecognizer.delegate = self;
[_topView addGestureRecognizer:rightRecognizer];
[_topView setUserInteractionEnabled:YES];
// [rightRecognizer release];
//........towards left Gesture recogniser for swiping.....//
UISwipeGestureRecognizer *leftRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(leftSwipeHandle:)];
leftRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[leftRecognizer setNumberOfTouchesRequired:1];
leftRecognizer.delegate = self;
[_topView addGestureRecognizer:leftRecognizer];
[_topView setUserInteractionEnabled:YES];
}
- (void)rightSwipeHandle:(UISwipeGestureRecognizer*)gestureRecognizer
{
//Do moving
NSLog(#"Right Swipe performed");
_showOld=YES;
[self updateInterface];
}
- (void)leftSwipeHandle:(UISwipeGestureRecognizer*)gestureRecognizer
{
// do moving
NSLog(#"Left Swipe performed");
_showOld=NO;
[self updateInterface];
}
The approach I suggest is listening to a user moving a finger events and firing specified method that would check if user's finger is on your button's view. Sample code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchedViewWithTouches:touches andEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchedViewWithTouches:touches andEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchedViewWithTouches:touches andEvent:event];
}
- (void)didTouchedButton:(NSSet *)touches andEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
UIView *touchedView;
for (UIView *view in self.subviews) {
if(CGRectContainsPoint(view.frame, touchLocation)) {
touchedView = view;
break;
}
}
if (view == self.showIncomingButton) {
[self showIncoming:self.showIncomingButton];
} else if (view == self.showOutcomingButton) {
[self howOutcoming:self.showOutcomingButton];
}
}
Note that you will have to add two outlets for your buttons (if you don't have any). Also, you can get rid off (id)sender in your IBAction calls.

How to filter a quick multiple tap for single Tap event and double tap event

I have below gestures setup:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(singleTapDetected:)];
singleTap.numberOfTapsRequired = 1;
[self addGestureRecognizer:singleTap];
UITapGestureRecognizer *doubleClick = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(doubleClickDetected:)];
doubleClick.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubleClick];
[singleTap requireGestureRecognizerToFail:doubleClick];
When I quickly tap 3 times, I find that it will be translated into one double tap event and one single tap event, and introduce a bug for my app.
I want something like if user clicks 3 or more times, only a double tap event will be triggered. Could some one help on this? Thank in advance.
I am answering this for the benefit of iOS 10 and Swift 3 developers.
Currently in iOS 10 with Swift 3 this problem does not exist. A triple tap if not handled is downgraded to double tap and the corresponding action is fired. Try the example below:
Swift 3 Solution:
doubleTap = UITapGestureRecognizer(target: self, action:#selector(self.doubleTapAction(_:)))
doubleTap.numberOfTapsRequired = 2
singleTap = UITapGestureRecognizer(target: self, action:#selector(self.singleTapAction(_:)))
singleTap.numberOfTapsRequired = 1
singleTap.require(toFail: doubleTap)
self.view.addGestureRecognizer(doubletap)
self.view.addGestureRecognizer(singleTap)
I created a custom recognizer like below also solved my problem:
#implementation MyTapGestureRecognizer
/**
* touchesBegan for custom taps will filter > 2 taps
*
* #param touches touches the recognizer gets
* #param event related event
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if ([touches count] != 1) {
return;
} else {
UITouch *touch = [touches anyObject];
if (touch.tapCount >= 3) {
self.state = UIGestureRecognizerStateFailed;
return;
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
}
- (void)reset {
[super reset];
}
#end
You can use the gesture recognizer's delegate to temporarily disable both gestures after a double click. The code below disables the gestures for 0.6 seconds after a double click.
You'll need a couple properties
#property int disableGestures;
#property CFTimeInterval timeStamp;
and some additional initialization
self.disableGestures = NO;
singleTap.delegate = self;
doubleClick.delegate = self;
and you'll need to conform to the <UIGestureRecognizerDelegate> protocol, and implement the shouldReceiveTouch method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ( self.disableGestures )
{
if ( CACurrentMediaTime() - self.timeStamp < 0.6 )
return NO;
self.disableGestures = NO;
}
return YES;
}
All that's left is to disable the gestures after a double click
- (void)doubleClickDetected:(UITapGestureRecognizer *)gesture
{
self.disableGestures = YES;
self.timeStamp = CACurrentMediaTime();
// normal processing for the double click goes here
}

Invoking touchesEnded: when tapped but not swiped

I'm wanting to invoke touchesEnded: method when the user TAPPED on a specific location on the screen but not when a user swipped on the screen. How can I achieve this?
From debugging, it seems that touchesEnded: is called when the user either tapped or swipped. I suppose I can add a local variable to track if the user had swipped like below but I think there is a more robust way of handling this:
BOOL trackSwipe = NO; // Local variable
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
trackSwipe = YES;
// do something
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if (trackSwipe == YES)
return;
else
{
CGPoint touchPoint = [[touches anyObject] locationInView:self];
// do additional work
}
}
I have also looked into adding UITapGestureRecognizer to invoke the tap selector but this approach doesn't allow me to find the touchPoint which is very important to have.
I appreciate any help on this. Many thanks.
You can try this using a UITapGestureRecognizer
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>
#end
ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapMethod:)];
recognizer.numberOfTapsRequired = 1;
recognizer.numberOfTouchesRequired = 1;
recognizer.delegate = self;
[self.view addGestureRecognizer:recognizer];
}
- (void)tapMethod:(UITapGestureRecognizer *)recognizer
{
CGPoint touch = [recognizer locationInView:yourViewHere];
NSLog(#"X: %f Y: %f",touch.x,touch.y);
}
In your viewDidLoad do this:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
Selector method:
-(void)dismissKeyboard{
[self.view endEditing:YES];
}
Using this method you can determine tap gesture.

How to intercept touches events on a MKMapView or UIWebView objects?

I'm not sure what I am doing wrong but I try to catch touches on a MKMapView object. I subclassed it by creating the following class :
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MapViewWithTouches : MKMapView {
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event;
#end
And the implementation :
#import "MapViewWithTouches.h"
#implementation MapViewWithTouches
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event {
NSLog(#"hello");
//[super touchesBegan:touches withEvent:event];
}
#end
But it looks like when I use this class, I see nothing on the Console :
MapViewWithTouches *mapView = [[MapViewWithTouches alloc] initWithFrame:self.view.frame];
[self.view insertSubview:mapView atIndex:0];
Any idea what I'm doing wrong?
The best way I have found to achieve this is with a Gesture Recognizer. Other ways turn out to involve a lot of hackish programming that imperfectly duplicates Apple's code, especially in the case of multitouch.
Here's what I do: Implement a gesture recognizer that cannot be prevented and that cannot prevent other gesture recognizers. Add it to the map view, and then use the gestureRecognizer's touchesBegan, touchesMoved, etc. to your fancy.
How to detect any tap inside an MKMapView (sans tricks)
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
}
}
After a day of pizzas, screamings, I finally found the solution! Very neat!
Peter, I used your trick above and tweaked it a little bit to finally have a solution which work perfectly with MKMapView and should work also with 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
I hope that will help some of you!
Cheers
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];
//.............
}
For a MKMapView the real working solution is with gesture recognization !
Me I wanted to stop updating the center of the map on my location when I drag the map or pinch to zoom.
So, create and add your gesture recognizer to the 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];
}
Look the UIGestureRecognizer Class Reference to see all available gesture recognizer.
Because we've defined the delegate to self, we have to implement the 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;
...
And override the methode gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer: in order to allow recognize multiple gestures simultaneously, if I understood right :
// Allow to recognize multiple gestures simultaneously (Implementation of the protocole UIGestureRecognizerDelegate)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Now write the methodes which will be called by our gesture recognizers :
// 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];
}
Just in case somebody is trying to do the same like me: I wanted to create an annotation at the point where the user taps. For that I used the UITapGestureRecognizer solution:
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];
.......
}
However, didTapOnMap: was also called when I tapped on the annotation and a new one would be created. The solution is to implement the UIGestureRecognizerDelegate:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[MKAnnotationView class]])
{
return NO;
}
return YES;
}
You probably will need to overlay a transparent view to catch the touches just like is done so often with UIWebView-based controls. The Map View already does a bunch of special things with a touch in order to allow the map to be moved, centered, zoomed, etc... that the messages are not getting bubbled up to your app.
Two other (UNTESTED) options I can think of:
1) Resign the first responder via IB and set it to "File's Owner" to allow file's Owner to respond to the touches. I an dubious that this will work because MKMapView extends NSObject, not UIView ans a result the touch events still may not get propagated up to you.
2) If you want to trap when the Map state changes (such as on a zoom) just implement the MKMapViewDelegate protocol to listen for particular events. My hunch is this is your best shot at trapping some interaction easily (short of implementing the transparent View over the Map). Do not forget to set the View Controller housing the MKMapView as the map's delegate (map.delegate = self).
Good Luck.
I haven't experimented, but there's a good chance MapKit is based around a class cluster, and therefore subclassing it is difficult and ineffective.
I'd suggest making the MapKit view a subview of a custom view, which should allow you to intercept touch events before they reach it.
So after half a day of messing around with this I found the following:
As everyone else found, pinching doesn't work. I tried both subclassing MKMapView and the method described above (intercepting it). And the result is the same.
In the Stanford iPhone videos, a guy from Apple says that many of the UIKit things will
cause alot of errors if you "transfer" the touch requests (aka the two methods described above), and you probably won't get it to work.
THE SOLUTION: is described here: Intercepting/Hijacking iPhone Touch Events for MKMapView. Basically you "catch" the event before any responder gets it, and interpret it there.
In 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)")
}
}
Make the MKMapView a subview of a custom view and implement
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
in the custom view to return self instead of the subview.
Thanks for the pizza and screamings - you saved me lots of time.
multipletouchenabled will work sporadically.
viewTouch.multipleTouchEnabled = TRUE;
In the end, I switched out the views when I needed to capture the touch (different point in time than needing pinchzooms):
[mapView removeFromSuperview];
[viewTouch addSubview:mapView];
[self.view insertSubview:viewTouch atIndex:0];
I notice that you can track the number and location of touches, and get the location of each in a view:
- (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];
}
Has anyone else tried using these values to update the map's zoom level? It would be a matter of recording the start positions, and then the finish locations, calculating the relative difference and updating the map.
I'm playing with the basic code provided by Martin, and this looks like it will work...
Here's what I put together, that does allow pinch zooms in the simulator (haven't tried on a real iPhone), but I think would be fine:
- (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];
}
The main idea is that if the user is using two fingers, you track the values. I record the starting and ending points in startPoints A and B. Then I record the current tracking points, and when I'm done, on touchesEnded, I can call a routine to calculate the relative lengths of the line between the points I start with, and the line between the point I end with using simple hypotenuse calc. The ratio between them is the zoom amount: I multiply the region span by that amount.
Hope it's useful to someone.
I took the idea of an "overlay" transparent view, from MystikSpiral's answer, and it worked perfectly for what I was trying to achieve; quick, and clean solution.
In short, I had a custom UITableViewCell (designed in IB) with a MKMapView on the left-hand-side and some UILabels on the right. I wanted to make the custom cell so you could touch it anywhere and this would push a new view controller. However touching the map didn't pass touches 'up' to the UITableViewCell until I simply added a UIView of the same size as the map view right on top of it (in IB) and made it's background the 'clear color' in code (don't think you can set clearColor in IB??):
dummyView.backgroundColor = [UIColor clearColor];
Thought it might help someone else; certainly if you want to achieve the same behaviour for a table view cell.
I don't understand why other answers are so complicated. The solution is really just one line:
mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(mapTapped)))
#objc func mapTapped() {}

Resources