iosmkmapviewuipangesturerecognizermkoverlay

Moving MKCircle on MKMapview and dragging MKMapview


I have a MKCircle on MKMapView. It is user-draggable, but if the user drags the area outside the circle, the map should be moving instead.

- (IBAction)createPanGestureRecognizer:(id)sender
{
    _mapView.scrollEnabled=NO;
    _panRecognizer = [[UIPanGestureRecognizer alloc]
                      initWithTarget:self action:@selector(respondToPanGesture:)];
    [_mapView addGestureRecognizer:_panRecognizer];
}

-(void)respondToPanGesture:(UIPanGestureRecognizer*)sender {

    static CGPoint originalPoint;

    if (sender.state == UIGestureRecognizerStateBegan) {
        CGPoint point = [sender locationInView:_mapView];
        CLLocationCoordinate2D tapCoordinate = [_mapView convertPoint:point toCoordinateFromView:_mapView];

        CLLocation *tapLocation = [[CLLocation alloc] initWithLatitude:tapCoordinate.latitude longitude:tapCoordinate.longitude];

        CLLocationCoordinate2D originalCoordinate = [_circle coordinate];
        CLLocation *originalLocation = [[CLLocation alloc] initWithLatitude:originalCoordinate.latitude longitude:originalCoordinate.longitude];

        if ([tapLocation distanceFromLocation:originalLocation] > [_circle radius]) {
            _mapView.scrollEnabled=YES;
            _isAllowedToMove=NO;
        }
        else if ([tapLocation distanceFromLocation:originalLocation] < [_circle radius]) {
            originalPoint = [_mapView convertCoordinate:originalCoordinate toPointToView:sender.view];
            _isAllowedToMove=YES;
        }
    }

    if (sender.state == UIGestureRecognizerStateChanged) {
        if (_isAllowedToMove)
        {
            CGPoint translation = [sender translationInView:sender.view];
            CGPoint newPoint    = CGPointMake(originalPoint.x + translation.x, originalPoint.y + translation.y);

            CLLocationCoordinate2D newCoordinate = [_mapView convertPoint:newPoint toCoordinateFromView:sender.view];

            MKCircle *circle2 = [MKCircle circleWithCenterCoordinate:newCoordinate radius:[_circle radius]];
            [_mapView addOverlay:circle2];
            [_mapView removeOverlay:_circle];
            _circle = circle2;
        }
    }

    if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed || sender.state == UIGestureRecognizerStateCancelled) {
        _mapView.scrollEnabled=NO;
        _isAllowedToMove=NO;
    }
}

Dragging the circle works fine, but when trying to drag the map, it stays still. My assumption is that

_mapView.scrollEnabled=YES;

makes the map draggable, but it needs another drag gesture to be started. How to accomplish this without losing the ability to move the circle?


Solution

  • To make it so that the map can be dragged if the user starts dragging outside the circle (and to not drag the map if the user starts dragging inside the circle), don't disable scrollEnabled from the beginning -- leave it on (until dragging starts and we can decide whether to disable or not).

    In order to leave scrollEnabled on initially (ie. let the map do its own panning) and to add our own gesture recognizer, we'll need to implement shouldRecognizeSimultaneouslyWithGestureRecognizer and return YES.

    The updated createPanGestureRecognizer: would look like this:

    - (IBAction)createPanGestureRecognizer:(id)sender
    {
        //_mapView.scrollEnabled=NO;  // <-- do NOT disable scrolling here
        _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(respondToPanGesture:)];
        _panRecognizer.delegate = self;  // <-- to implement shouldRecognize
        [_mapView addGestureRecognizer:_panRecognizer];
    }
    
    -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        return YES;
    }
    

    Then in our gesture handler, when the gesture begins, enable or disable scrollEnabled based on where the pan starts (outside or inside the circle):

    if ([tapLocation distanceFromLocation:originalLocation] > [_circle radius]) {
        _mapView.scrollEnabled=YES;
        _isAllowedToMove=NO;
    }
    else //if ([tapLocation distanceFromLocation:originalLocation] < [_circle radius]) {
    //NOTE: It's not really necessary to check if distance is less than radius
    //      in the ELSE part since in the IF we checked if it's greater-than.
    //      If we get to the ELSE, we know distance is <= radius.
    //      Unless for some reason you want to handle the case where
    //      distance exactly equals radius differently but this is unlikely.
    {
        originalPoint = [_mapView convertCoordinate:originalCoordinate toPointToView:sender.view];
        _mapView.scrollEnabled=NO;  // <-- disable scrolling HERE
        _isAllowedToMove=YES;
    }
    

    Finally, always re-enable scrollEnabled when a gesture ends (just in case).
    When the next gesture starts, it will be re-disabled if necessary:

    if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed || sender.state == UIGestureRecognizerStateCancelled) {
        _mapView.scrollEnabled=YES;  // <-- enable instead of disable
        _isAllowedToMove=NO;
    }
    

    Note that if the user is allowed to call createPanGestureRecognizer: multiple times, you should probably move the creation and addition of the recognizer somewhere else (viewDidLoad maybe) otherwise multiple instances will get added to the map.

    Alternatively, change the button to a toggle so that if "move" mode is ON, it removes the gesture recognizer from the map instead of creating and adding it.