Mapbox-gl-native: allow for iOS tap gesture

Created on 26 Mar 2015  Â·  14Comments  Â·  Source: mapbox/mapbox-gl-native

Right now our manual marker hit-testing tap gesture interferes with a single-tap the user might want to attach to the map. We need to indicate internally when a tap happens off of a marker while also no markers are selected, and pass it on.

bug iOS

Most helpful comment

Here's a workaround that allows a custom single tap, while also passing along taps to annotations:

#import "ViewController.h"
#import "Mapbox.h"

@interface ViewController () <MGLMapViewDelegate, UIGestureRecognizerDelegate>

@property (nonatomic) MGLMapView *mapView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
    self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    self.mapView.delegate = self;

    [self.view addSubview:self.mapView];

    // double tapping zooms the map, so ensure that can still happen
    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:nil];
    doubleTap.numberOfTapsRequired = 2;
    [self.mapView addGestureRecognizer:doubleTap];

    // delay single tap recognition until it is clearly not a double
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [singleTap requireGestureRecognizerToFail:doubleTap];
    singleTap.delegate = self;
    [self.mapView addGestureRecognizer:singleTap];

    // also, long press for the hell of it
    [self.mapView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]];
}

- (void)handleSingleTap:(UITapGestureRecognizer *)tap
{
    // convert tap location (CGPoint)
    // to geographic coordinates (CLLocationCoordinate2D)
    CLLocationCoordinate2D location = [self.mapView convertPoint:[tap locationInView:self.mapView]
                                            toCoordinateFromView:self.mapView];

    NSLog(@"You tapped at: %.5f, %.5f", location.latitude, location.longitude);
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    // you could check for specific gestures here, but ¯\_(ツ)_/¯
    return YES;
}

- (void)handleLongPress:(UILongPressGestureRecognizer *)longPress
{
    // drop a marker annotation
    if (longPress.state == UIGestureRecognizerStateBegan)
    {
        MGLPointAnnotation *point = [MGLPointAnnotation new];
        point.coordinate = [self.mapView convertPoint:[longPress locationInView:longPress.view]
                                 toCoordinateFromView:self.mapView];
        point.title = @"Dropped Marker";
        point.subtitle = [NSString stringWithFormat:@"lat: %.3f, lon: %.3f", point.coordinate.latitude, point.coordinate.longitude];
        [self.mapView addAnnotation:point];
        [self.mapView selectAnnotation:point animated:YES];
    }
}

- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation
{
    return YES;
}

@end

Remember to set your mapview and gesture delegates.

All 14 comments

Thread : Crashed: com.apple.root.background-qos
0  libobjc.A.dylib                0x0000000193bdbbd0 objc_msgSend + 16
1  CoreFoundation                 0x0000000182be9e7c __NSFastEnumerationMutationHandler + 36
2  UIKit                          0x00000001875fba40 -[UIGestureRecognizer locationInView:] + 268
3  Embark                         0x00000001002476c0 __46-[MGLMapView trackGestureEvent:forRecognizer:]_block_invoke (MGLMapView.mm:1017)
4  libdispatch.dylib              0x0000000194219994 _dispatch_call_block_and_release + 24
5  libdispatch.dylib              0x0000000194219954 _dispatch_client_callout + 16
6  libdispatch.dylib              0x0000000194226780 _dispatch_root_queue_drain + 1848
7  libdispatch.dylib              0x0000000194227c4c _dispatch_worker_thread3 + 108
8  libsystem_pthread.dylib        0x00000001943f922c _pthread_wqthread + 816

Pushing this since markers & sprites will be getting some rework in b3.

Here's a workaround that allows a custom single tap, while also passing along taps to annotations:

#import "ViewController.h"
#import "Mapbox.h"

@interface ViewController () <MGLMapViewDelegate, UIGestureRecognizerDelegate>

@property (nonatomic) MGLMapView *mapView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
    self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    self.mapView.delegate = self;

    [self.view addSubview:self.mapView];

    // double tapping zooms the map, so ensure that can still happen
    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:nil];
    doubleTap.numberOfTapsRequired = 2;
    [self.mapView addGestureRecognizer:doubleTap];

    // delay single tap recognition until it is clearly not a double
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [singleTap requireGestureRecognizerToFail:doubleTap];
    singleTap.delegate = self;
    [self.mapView addGestureRecognizer:singleTap];

    // also, long press for the hell of it
    [self.mapView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]];
}

- (void)handleSingleTap:(UITapGestureRecognizer *)tap
{
    // convert tap location (CGPoint)
    // to geographic coordinates (CLLocationCoordinate2D)
    CLLocationCoordinate2D location = [self.mapView convertPoint:[tap locationInView:self.mapView]
                                            toCoordinateFromView:self.mapView];

    NSLog(@"You tapped at: %.5f, %.5f", location.latitude, location.longitude);
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    // you could check for specific gestures here, but ¯\_(ツ)_/¯
    return YES;
}

- (void)handleLongPress:(UILongPressGestureRecognizer *)longPress
{
    // drop a marker annotation
    if (longPress.state == UIGestureRecognizerStateBegan)
    {
        MGLPointAnnotation *point = [MGLPointAnnotation new];
        point.coordinate = [self.mapView convertPoint:[longPress locationInView:longPress.view]
                                 toCoordinateFromView:self.mapView];
        point.title = @"Dropped Marker";
        point.subtitle = [NSString stringWithFormat:@"lat: %.3f, lon: %.3f", point.coordinate.latitude, point.coordinate.longitude];
        [self.mapView addAnnotation:point];
        [self.mapView selectAnnotation:point animated:YES];
    }
}

- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation
{
    return YES;
}

@end

Remember to set your mapview and gesture delegates.

@friedbunny how can we detect if singleTap was tapped or annotation on mapView? I need that singleTap will be handled only on empty area of map (without pins), and didSelectAnnotation called when i tap on pin. Thanks

@Gurbo That’s somewhat tricky and the fact that you can’t easily detect non-annotation taps is the reason this issue is still open. You could watch mapView.selectedAnnotations and cancel your gesture if it changes, but that’s not ideal (or foolproof) and we need to provide a better way.

Hi my requirement is exactly same as @Grubo, But I found that on android we have method like this

mapboxMap.setOnMapClickListener(new MapboxMap.OnMapClickListener() {
            public void onMapClick(@NonNull LatLng point) {
                Toast.makeText(getActivity(),"on Tap "+point.getLatitude(),Toast.LENGTH_LONG).show();
            }
        });

and along with that

mapboxMap.setInfoWindowAdapter(new MapboxMap.InfoWindowAdapter() { ... })will display the tapped annotation.

Don't we have same kind of concept in iOS ?
If you already made this android kind of feature, please let me know the branch. If not can you make this feature ASAP, we are stuck without that.

+1 This would really help on an iOS project I am working on. It would be great to have a separate mapview delegate function for when the map is tapped and no annotations have been selected by the tap.

I made some work around and posted on stackoverflow. check Here

I cloned the release-ios-v3.3.0 and created package using Building the SDK and added one delegate method in MGLMapViewDelegate.h as per my need something like that.
- (void)mapView:(MGLMapView *)mapView tapOnNonAnnotationAreaWithPoints:(CGPoint)points

and in MGLMapView.mm I updated the code like that,

    {
        if(self.selectedAnnotation)
            [self deselectAnnotation:self.selectedAnnotation animated:YES];
        else if([self.delegate respondsToSelector:@selector(mapView:tapOnNonAnnotationAreaWithPoints:)]){
            [self.delegate mapView:self tapOnNonAnnotationAreaWithPoints:tapPoint];
        }
    }

which is in - (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap method.

Its working for me, as I am able to detect single tap on non annotation area. later I convert the passed points to geo coordinates, so that I work with newly tapped coordinate.

Note

  1. Newly Created library size is about 48.7 MB where as downloaded library was 38.3 MB.
  2. Style is not working properly, you can see in attached pic. (Tried with different Style URL but none of it is same as previous)
  3. I feel lack in App performance, Zooming and Panning is not smooth as the dynamic library gave by Mapbox guys.

I am still exploring. lemme know if you wanna me to check/explore something.
img_0454
img_0455

@momin96 The differences in your build are likely because it’s being built for debug, as opposed to release — add BUILDTYPE=Release to the build command and you should see improvements.

More recent revisions of the developing docs are a bit clearer on this.

I need this feature too. when user tap on map, should return array of features, so I could loop through each feature, extract the properties and create a annotation marker to show properties.

FYI @hoogw once 3.4.0 is released, you won't need to add an annotation but can merely change styling on the features in the core map. But yes, right now you cannot associate a single tap with the call to -visibleFeaturesAtPoint:.

@incanus So the solution could be: 1) to get the point (lat, long), use add single tap gesture.

2) to get array of feature at point, use visibleFeaturesAtPoint

I am using v3.3.4, so on 3.4.0, no need add annotation, mean there is another way to show properties? Can you give more details, how that works?

Gestures now properly fail-through after #7246.

Was this page helpful?
0 / 5 - 0 ratings