views:

3985

answers:

6

Is there a bug in the 3.0 SDK that disables real-time zooming and intercepting the zoom-in gesture for the MKMapView? I have some real simple code so I can detect tap events, but there are two problems:

  1. zoom-in gesture is always interpreted as a zoom-out
  2. none of the zoom gestures update the Map's view in realtime.

In hitTest, if I return the "map" view, the MKMapView functionality works great, but I don't get the opportunity to intercept the events.

Any ideas?

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

MKMapView does not respond to the touch methods listed above...

Daniel
I don't believe that. The map pans and zooms out ok by passing the events to the map view. When "map" is replaced with "super", the map has absolutely no interactivity.The whole MapKit framework feels kinda half-done IMO. There is this problem and there should be a callback method in the MapViewDelegate for annotation selected.
As far as i know, a bunch of apples custom UIs such (UIScrollView, MKMapView) dont let you get a hold on the touch events. I think there is a callback methods in MapViewDelegate for selected annotation...
Daniel
of course it responds to touches, it just doesn't offer a way to intercept them unless you inherit from it or wrap it
slf
I didnt say it didnt respond to touches, i said it does not respond to the touch methods listed above, which means you cant intercep t the touches...
Daniel
And btw inherting from mkmapview does not allow you to intercept touches either
Daniel
+1  A: 

Try

[super touchesEnded:touches withEvent:event];

instead of

[map touchesEnded:touches withEvent:event];

And apply this idea to all of the touch event methods. That way, the touches should travel down the responder chain, and peace will be restored.

Joe V
+4  A: 

Hi,

I had the same problem - I wanted to draw map scales on top of Map View. In order to do it I had to intercept the touch events, and then send them back to the Map View. Unfortunately, when the MKMapView isn't the original receiver of the events, some smooth panning and zooming animations are not working any more.

However I have found a solution to this problem - a bit hacky but works: 1. I have put my MapScales UIView on top of MKMapView, and turned off receiving events in it, so that underlying MKMapView received the events by default. 2. I have subclassed UIWindow with MyMainWindow class and in it I have overriden the method:

- (void) sendEvent:(UIEvent*)event {
  [super sendEvent:event];
  [self send_the_event_also_to_my_MapScales_component_with_use_of_listener_design_pattern];
}
  1. I have made the main window of my application an instasnce of MyMainWindow.

In this way my MapScales component receives and can react to all the touch events, and at the same time it is not spoiling the underlying MKMapView :)

chomasek
this is not a hack. this is the proper way to solve this. composition, not inheritance. just because you can inherit from mkmapview doesn't mean you should
slf
How do you do this when the map is not the only view in the window, i.e. you have a navigation bar or something. You don't want clicks to the navigation bar to trickle down to the MapScales. Is the only way to check size?
DevDevDev
+1  A: 

To extend up on @chomasek's answer, in order to only process those touches for the map view, I do the following:

  1. Give the map view a tag, like 99
  2. When I get touch, traverse up the view hierarchy of the receiving view, looking for a view with the above tag.

Here is the code:

// this is in the view controller containing the map view
// kICTagForMapView is just a constant
_mapView.tag = kICTagForMapView;

Then in 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
freespace
A: 

import

import "UIViewTouch.h"

import

@interface MapTouchAppDelegate : NSObject { 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

@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

this code will detect touches as well as zooming...........

vijay
A: 

The best way I have found to achieve this is with a Gesture Recognizer. It's unclear if you want to recognize zoom events yourself or just detect when the user is zooming/panning.

I don't need to detect a map pan or zoom--i just care if the user has put a finger down anywhere in the MKMapView. If you need to detect zooming or panning with 100% recall and precision, it might be more complicated than this. What we really need is an open source implementation of MKMapView so we can add this to the delegate, among many other features.

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 deal with the relevant touch events as you desire.

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
gonzojive