Domanda

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?

È stato utile?

Soluzione

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.

Altri suggerimenti

Updated answer using swift 4

Includes some changes: panGesture.maximumNumberOfTouches = 1 to avoid detecting pinch to zoom as pan gesture Screen points used for active area where pan gesture can be started.

private func addPanGestureRecognizer(to map: MKMapView) {
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(respondToPanGesture(_:)))
    panGesture.maximumNumberOfTouches = 1
    panGesture.delegate = self
    map.addGestureRecognizer(panGesture)
}

@objc private func respondToPanGesture(_ sender: UIPanGestureRecognizer) {
    guard let map = self.map else {
        return
    }

    let circlePoint = map.convert(startPoint, toPointTo: map) // circle center in View coordinate system

    switch sender.state {
    case .began:
        let point = sender.location(in: map)
        // Set touch radius in points (20), not meters to support different map zoom levels
        if point.radiusContainsPoint(radius: 20, point: circlePoint) {
            // set class property to store initial circle position before start dragging
            initialDragPoint = circlePoint
            map.isScrollEnabled = false
            isDragEnabled = true
        }
        else {
            map.isScrollEnabled = true
            isDragEnabled = false
        }
    case .changed:
        if isDragEnabled {
            let translation = sender.translation(in: map)
            let newPoint = CGPoint(x: initialDragPoint.x + translation.x, y: initialDragPoint.y + translation.y)
            let updatedCoordinate = map.convert(newPoint, toCoordinateFrom: map)
            let newOverlay = makeCircleOverlay(at: updatedCoordinate)
            map.remove(circleOverlay)
            map.add(newOverlay)
            circleOverlay = newOverlay
        }
    case .ended, .failed, .cancelled:
        map.isScrollEnabled = true
        isDragEnabled = false
    case .possible:
        break
    }
}

// MARK: - UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top