Prevent scrolling in a MKMapView, also when zooming
Asked Answered
S

6

22

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 :

  • Subclassing the MKMapKit to disable the scroll view inside it.
  • Implementing –mapView:regionWillChangeAnimated: to enforce the center.
  • Disabling scrollEnabled.

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 ?

Sortie answered 6/8, 2012 at 14:41 Comment(1)
I've had to do this before but did handled it a different way. I disabled interaction with MKMapView and added pinch gesture recognizers to a view above it. I then converted the pinch gestures to a corresponding zoom level. So pretty much rolling your own pinch-to-zoom functionally. Seeing as this doesn't directly answer your question, if it's a viable option for you i'll post in the answers with code.Letterpress
S
31

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.

Spirochete answered 14/8, 2012 at 14:20 Comment(2)
I don't think this solution is so great. It feels very clunky compared to the default zoom provided by apple. E.g, there is a lag between pinch and the screen updating.Innuendo
change animated:YES to animated:No in last line [self.mapView setRegion:MKCoordinateRegionMake(originalRegion.center, span) animated:YES]; and its working smoothly.Qualitative
Q
4

Swift 3.0 version of @Paras Joshi answer https://mcmap.net/q/574959/-prevent-scrolling-in-a-mkmapview-also-when-zooming

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)

    }
}
Qualitative answered 1/2, 2017 at 11:33 Comment(0)
C
1

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

Crysta answered 6/8, 2012 at 15:46 Comment(0)
P
1

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.

Phyllida answered 9/8, 2012 at 13:48 Comment(0)
R
1

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.

Roney answered 14/5, 2015 at 0:32 Comment(0)
G
1

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 
}
Gonroff answered 23/1, 2016 at 1:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.