Nils Munch
Nils Munch

Reputation: 8845

Prevent scrolling in a MKMapView, also when zooming

The scrollEnabled seems to be breakable once the user starts pinching in a MKMapView.

You still can't scroll with one finger, but if you scroll with two fingers while zooming in and out, you can move the map.

I have tried :

but with no luck.

Can anyone tell me a sure way to ONLY have zooming in a MKMapView, so the center point always stays in the middle ?

Upvotes: 22

Views: 9677

Answers (6)

Afzaal Ahmad
Afzaal Ahmad

Reputation: 845

Swift 3.0 version of @Paras Joshi answer https://stackoverflow.com/a/11954355/3754976

with small animation fix.

class MapViewZoomCenter: MKMapView {

    var originalRegion: MKCoordinateRegion!

    override func awakeFromNib() {
       self.configureView()
    }

    func configureView() {
        isZoomEnabled = false
        self.registerZoomGesture()
    }

    ///Register zoom gesture
    func registerZoomGesture() {
        let recognizer = UIPinchGestureRecognizer(target: self, action:#selector(MapViewZoomCenter.handleMapPinch(recognizer:)))
        self.addGestureRecognizer(recognizer)
    }

    ///Zoom in/out map
    func handleMapPinch(recognizer: UIPinchGestureRecognizer) {

        if (recognizer.state == .began) {
            self.originalRegion = self.region;
        }

        var latdelta: Double = originalRegion.span.latitudeDelta / Double(recognizer.scale)
        var londelta: Double = originalRegion.span.longitudeDelta / Double(recognizer.scale)

        //set these constants to appropriate values to set max/min zoomscale
        latdelta = max(min(latdelta, 80), 0.02);
        londelta = max(min(londelta, 80), 0.02);

        let span = MKCoordinateSpanMake(latdelta, londelta)

        self.setRegion(MKCoordinateRegionMake(originalRegion.center, span), animated: false)

    }
}

Upvotes: 4

Haitao
Haitao

Reputation: 132

I tried this and it works.

First create a property:

var originalCenter: CLLocationCoordinate2D?

Then in regionWillChangeAnimated, check if this event is caused by a UIPinchGestureRecognizer:

func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
    let firstView = mapView.subviews.first
    if let recognizer = firstView?.gestureRecognizers?.filter({ $0.state == .Began || $0.state == .Ended }).first as? UIPinchGestureRecognizer {
        if recognizer.scale != 1.0 {
            originalCenter = mapView.region.center
        }
    }
}

Then in regionDidChangeAnimated, return to original region if a pinch gesture caused the region changing:

func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if let center = originalCenter {
        mapView.setRegion(MKCoordinateRegion(center: center, span: mapView.region.span), animated: true)
        originalCenter = nil
        return
    }
// your other code 
}

Upvotes: 1

Travis Griggs
Travis Griggs

Reputation: 22252

I did not have a lot of luck with any of these answers. Doing my own pinch just conflicted too much. I was running into cases where the normal zoom would zoom farther in than I could do with my own pinch.

Originally, I tried as the original poster to do something like:

- (void) mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    MKCoordinateRegion region = mapView.region;
    //...
    // adjust the region.center 
    //...
    mapView.region = region;
}

What I found was that that had no effect. I also discovered through NSLogs that this method will fire even when I set the region or centerCoordinate programmatically. Which led to the question: "Wouldn't the above, if it DID work go infinite?"

So I'm conjecturing and hypothesizing now that while user zoom/scroll/rotate is happening, MapView somehow suppresses or ignores changes to the region. Something about the arbitration renders the programmatic adjustment impotent.

If that's the problem, then maybe the key is to get the region adjustment outside of the regionDidChanged: notification. AND since any adjustment will trigger another notification, it is important that it be able to determine when not to adjust anymore. This led me to the following implementation (where subject is supplying the center coordinate that I want to stay in the middle):

- (void) recenterMap {
    double latDiff = self.subject.coordinate.latitude self.mapView.centerCoordinate.latitude;
    double lonDiff = self.subject.coordinate.longitude - self.mapView.centerCoordinate.longitude;
    BOOL latIsDiff = ABS(latDiff) > 0.00001;
    BOOL lonIsDiff = ABS(lonDiff) > 0.00001;
    if (self.subject.isLocated && (lonIsDiff || latIsDiff)) {
        [self.mapView setCenterCoordinate: self.subject.coordinate animated: YES];
    }
}

- (void) mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    if (self.isShowingMap) {
        if (self.isInEdit) {
            self.setLocationButton.hidden = NO;
            self.mapEditPrompt.hidden = YES;
        }
        else {
            if (self.subject.isLocated) { // dispatch outside so it will happen after the map view user events are done
                 dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
                    [self recenterMap];
                });
            }
        }
    }
}

The delay where it slides it back can vary, but it really does work pretty well. And lets the map interaction remain Apple-esque while it's happening.

Upvotes: 1

Felix
Felix

Reputation: 35384

You can try to handle the pinch gestures yourself using a UIPinchGestureRecognizer:

First set scrollEnabled and zoomEnabled to NO and create the gesture recognizer:

UIPinchGestureRecognizer* recognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
                                                                                 action:@selector(handlePinch:)];
[self.mapView addGestureRecognizer:recognizer];

In the recognizer handler adjust the MKCoordinateSpan according to the zoom scale:

- (void)handlePinch:(UIPinchGestureRecognizer*)recognizer
{
    static MKCoordinateRegion originalRegion;
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        originalRegion = self.mapView.region;
    }    

    double latdelta = originalRegion.span.latitudeDelta / recognizer.scale;
    double londelta = originalRegion.span.longitudeDelta / recognizer.scale;

    // TODO: set these constants to appropriate values to set max/min zoomscale
    latdelta = MAX(MIN(latdelta, 80), 0.02);
    londelta = MAX(MIN(londelta, 80), 0.02);
    MKCoordinateSpan span = MKCoordinateSpanMake(latdelta, londelta);

    [self.mapView setRegion:MKCoordinateRegionMake(originalRegion.center, span) animated:YES];
}

This may not work perfectly like Apple's implementation but it should solve your issue.

Upvotes: 31

Cezar
Cezar

Reputation: 56322

I've read about this before, though I've never actually tried it. Have a look at this article about a MKMapView with boundaries. It uses two delegate methods to check if the view has been scrolled by the user.

http://blog.jamgraham.com/blog/2012/04/29/adding-boundaries-to-mkmapview

The article describes an approach which is similar to what you've tried, so, sorry if you've already stumbled upon it.

Upvotes: 1

Caleb
Caleb

Reputation: 124997

Try implementing –mapView:regionWillChangeAnimated: or –mapView:regionDidChangeAnimated: in your map view's delegate so that the map is always centered on your preferred location.

Upvotes: 1

Related Questions