iOS - How to limit the MapView to a specific region?

25,104

Solution 1

By implementing :

-(void) mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated

You should be able to reset region to your specific one.

Look at that answer to have an example of how to do this.


Another solution, that brings more precise events but is more complex (I'm currently using this successfully for another need) is to subclass MKMapView and override UIScrollViewDelegate protocol.

If you do that, make sure to call super implementation like that :

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  if ([super respondsToSelector:@selector(scrollViewWillBeginDragging:)])
    [super scrollViewWillBeginDragging:scrollView];
  // do what you want
}

Note: While the protocol is public, MKMapView implementation is unknown and might differ with iOS versions, so it is more safe to check with respondsToSelector: before. You must call super implementations or Map will just not work properly.

Solution 2

After trying different ways of limited MKMapView I've concluded that using mapDidChange, and resetting if you're center point goes outside of the boundaries works best, with animated: YES

Here's how I do it (Using the New Zealand lat/Long span/center).

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{ 
  if ((mapView.region.span.latitudeDelta > 15.589921 ) || (mapView.region.span.longitudeDelta > 175.836914) ) {
    CLLocationCoordinate2D centerCoord = CLLocationCoordinate2DMake(-41.162114, 172.836914);

    MKCoordinateSpan spanOfNZ = MKCoordinateSpanMake(13.589921, 14.062500 );

    MKCoordinateRegion NZRegion = MKCoordinateRegionMake(centerCoord, spanOfNZ);

    [mapView setRegion: NZRegion animated: YES];
  }

 if (abs(abs(mapView.region.center.latitude) - 41.162114) > (13.589921 / 2) ) {
    CLLocationCoordinate2D centerCoord = CLLocationCoordinate2DMake(-41.162114, 172.836914);

    MKCoordinateSpan spanOfNZ = MKCoordinateSpanMake(13.589921, 14.062500 );

    MKCoordinateRegion NZRegion = MKCoordinateRegionMake(centerCoord, spanOfNZ);

    [mapView setRegion: NZRegion animated: YES];

  }

  if (abs(abs(mapView.region.center.longitude) - 172.836914) > (14.062500 / 2) ) {
    CLLocationCoordinate2D centerCoord = CLLocationCoordinate2DMake(-41.162114, 172.836914);

     MKCoordinateSpan  spanOfNZ = MKCoordinateSpanMake(13.589921, 14.062500 );

     MKCoordinateRegion NZRegion = MKCoordinateRegionMake(centerCoord, spanOfNZ);

     [mapView setRegion: NZRegion animated: YES];
  }
}
Share:
25,104

Related videos on Youtube

cweinberger
Author by

cweinberger

passionate developer with focus on mobile. iOS/macOS development web development (PHP, JS, Java, ...) C/C++/Java

Updated on July 09, 2022

Comments

  • cweinberger
    cweinberger almost 2 years

    I have the following problem:

    I have a "drawn map" (image) which I add to the MapView as an Overlay. No Problem with that.. but I need to limit the MapView to the region of the Overlay, so a user isn't able to scroll/zoom outside of this region.. but it should be possible to scroll/zoom inside the "bounds" of the overlay - means I cannot just disable zoom/scrolling for the MapView.

    Are there any ideas/solution on this topic? The reason for using the MapView/-Kit is that I need to add various POIs to the custom map. This may become more complex when just using an ImageView+ScrollView for presenting the custom map.

    I've researched alot on this topic, but I didn't found a nice solution.

    Any help is appreciated!

    Best Regards, Christian

    Edit: This is our solution: You supply a topleft and a bottomright coordinate to limit the map. The (minimum) zoomlevel is also limited. I've deactivated decelerating and you are able to bounce a bit out of the map (for better performance/ux). I added a ~1km grey border to the overlay so the user isnÄt able to see the orignal worldmap of google.

    LimitedMapView.h:

    #import <Foundation/Foundation.h>
    #import <MapKit/MapKit.h>
    
    
    @interface LimitedMapView : MKMapView <UIScrollViewDelegate>{
    
    }
    
    @property (nonatomic, assign) CLLocationCoordinate2D topLeftCoordinate;
    @property (nonatomic, assign) CLLocationCoordinate2D bottomRightCoordinate;
    
    
    @end
    

    LimitedMapView.m:

    #import "LimitedMapView.h"
    
    @implementation LimitedMapView
    
    @synthesize topLeftCoordinate, bottomRightCoordinate;
    
    - (void)scrollViewDidZoom:(UIScrollView *)scrollView{
    
        if([super respondsToSelector:@selector(scrollViewDidZoom:)]) [super scrollViewDidZoom:scrollView];
    
        if ([self region].span.latitudeDelta > 0.002401f || [self region].span.longitudeDelta > 0.003433f) {
    
            CLLocationCoordinate2D center = self.centerCoordinate;
            MKCoordinateSpan span = MKCoordinateSpanMake(0.002401f, 0.003433f);
    
            self.region = MKCoordinateRegionMake(center, span);
    
        }
    
    }
    
    -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    
        if([super respondsToSelector:@selector(scrollViewDidEndDragging:)]) [super scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
    
        MKCoordinateRegion currentRegion = self.region;
        bool changeRegionLong = YES;
        bool changeRegionLat = YES;
    
        // LONGITUDE    
        if((currentRegion.center.longitude - (currentRegion.span.longitudeDelta/2)) < self.topLeftCoordinate.longitude) {
    
            currentRegion.center.longitude = (topLeftCoordinate.longitude + (currentRegion.span.longitudeDelta/2));
    
        } else if((currentRegion.center.longitude + (currentRegion.span.longitudeDelta/2)) > self.bottomRightCoordinate.longitude) {
    
            currentRegion.center.longitude = (bottomRightCoordinate.longitude - (currentRegion.span.longitudeDelta/2));
    
        } else {
    
            changeRegionLong = NO;
    
        }
    
        // LATITUDE    
        if((currentRegion.center.latitude + (currentRegion.span.latitudeDelta/2)) > self.topLeftCoordinate.latitude) {
    
            currentRegion.center.latitude = (topLeftCoordinate.latitude - (currentRegion.span.latitudeDelta/2));
    
        } else if((currentRegion.center.latitude - (currentRegion.span.latitudeDelta/2)) < self.bottomRightCoordinate.latitude) {
    
            currentRegion.center.latitude = (bottomRightCoordinate.latitude + (currentRegion.span.latitudeDelta/2));
    
        } else {
    
            changeRegionLat = NO;
    
        }
    
        if(changeRegionLong || changeRegionLat) [self setRegion:currentRegion animated:YES];
    
    }
    
    -(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
    
        [scrollView setContentOffset:scrollView.contentOffset animated:YES];
    }
    
    @end
    
  • cweinberger
    cweinberger about 13 years
    Hmm.. I think this delegate will fire when I start moving the Map. Unfortunalety at this moment I don't know what the user will do with the Map (could be a valid zoom/scroll gesture). And the -(void) mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated fires too late. Or am I totally wrong with this?!
  • Vincent Guerci
    Vincent Guerci about 13 years
    added another solution that might help ;)
  • Vincent Guerci
    Vincent Guerci about 13 years
    that's not so nice, just dirty "hacking" a bit some apis that are not open enough for certain uses... aah if only apple and google were still friends we might have a featured and stable MapKit...
  • cweinberger
    cweinberger about 13 years
    in this case, anything which will work and look ok (for the end-user), is a nice solution to me. but you're right... there definitely should be a method included to limit the viewable region :/
  • cweinberger
    cweinberger about 13 years
    Thanks for your hints. Added my solution to the question. For the moment it's ok for us :).
  • David van Dugteren
    David van Dugteren almost 13 years
    I should mention the above code also has a limit on the zoom level.
  • Undistraction
    Undistraction almost 12 years
    For anyone else trying to get this to work, this is definitely the way to go.
  • Mazyod
    Mazyod over 11 years
    This turned into an infinite recursion to me. I had to unset the map delegate from the NIB, and set the delegate in viewDidLoad after setting the initial region.
  • bean
    bean about 11 years
    Thanks for this solution. Small note - If anyone else has trouble with this in future I had to change abs(x) to fabs(x) as lon/lats are doubles and abs(x) returns an int. This doesn't seem to matter with the values you have as they are so large, but I was working with much smaller regions!
  • coolcool1994
    coolcool1994 about 10 years
    This crashes my app. And how did you get this value (mapView.region.span.latitudeDelta > 15.589921 )?? 15.589921?? And should mapView be self.mapView?
  • Moritz
    Moritz almost 10 years
    While this has definitely worked for me (and you have received my upvote) it is overly verbose and there is one too many absolute value function. (Do use fabs(x) as @bean pointed out). I've created a more legible gist for anyone interested: gist.github.com/Alp-Phone/e11cca67e77285566d4d

Related