94

I've got several annotations I want to add to my MKMapView (it could 0-n items, where n is generally around 5). I can add the annotations fine, but I want to resize the map to fit all annotations onscreen at once, and I'm not sure how to do this.

I've been looking at -regionThatFits: but I'm not quite sure what to do with it. I'll post some code to show what I've got so far. I think this should be a generally straightforward task but I'm feeling a bit overwhelmed with MapKit so far.

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{

location = newLocation.coordinate;
//One location is obtained.. just zoom to that location

MKCoordinateRegion region;
region.center = location;

//Set Zoom level using Span
MKCoordinateSpan span;
span.latitudeDelta = 0.015;
span.longitudeDelta = 0.015;
region.span = span;
// Set the region here... but I want this to be a dynamic size
// Obviously this should be set after I've added my annotations
[mapView setRegion:region animated:YES];

// Test data, using these as annotations for now
NSArray *arr = [NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil];
float ex = 0.01;
for (NSString *s in arr) {
    JBAnnotation *placemark = [[JBAnnotation alloc] initWithLat:(location.latitude + ex) lon:location.longitude];
    [mapView addAnnotation:placemark];
    ex = ex + 0.005;
}
    // What do I do here?
    [mapView setRegion:[mapView regionThatFits:region] animated:YES];
}

Notice, this all happens as I receive a location update... I don't know if that's an appropriate place to do this. If not, where would be a better place? -viewDidLoad?

Thanks in advance.

TheNeil
  • 1,985
  • 2
  • 17
  • 36
jbrennan
  • 11,523
  • 14
  • 69
  • 110

24 Answers24

137

The link posted by Jim is now dead, but i was able to find the code (which I had bookmarked somewhere). Hope this helps.

- (void)zoomToFitMapAnnotations:(MKMapView *)mapView { 
    if ([mapView.annotations count] == 0) return; 

    CLLocationCoordinate2D topLeftCoord; 
    topLeftCoord.latitude = -90; 
    topLeftCoord.longitude = 180; 

    CLLocationCoordinate2D bottomRightCoord; 
    bottomRightCoord.latitude = 90; 
    bottomRightCoord.longitude = -180; 

    for(id<MKAnnotation> annotation in mapView.annotations) { 
        topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude); 
        topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude); 
        bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude); 
        bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude); 
    } 

    MKCoordinateRegion region; 
    region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5; 
    region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;      

    // Add a little extra space on the sides
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; 

    region = [mapView regionThatFits:region]; 
    [mapView setRegion:region animated:YES]; 
}
Jagat Dave
  • 1,633
  • 3
  • 22
  • 30
Mustafa
  • 20,108
  • 39
  • 139
  • 208
  • 14
    I could kiss you. This just saved me a bunch of time. I added the code to the above to handle 1 location. It got a little up close and personal. I'll post that as a response since comments tend to chew up code. – Michael Reed Jan 31 '12 at 04:29
  • Thank you very much. I added this to a subclass of `MKMapView` and changed the method to `- (void) zoomToFitAnnotations:(BOOL)animated`. Works perfectly! – simonbs Apr 24 '12 at 15:12
  • 1
    it's working very well. also it's useful. you can change zoom out or zoom in value. so region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1; /// change value. when you increase value : zoom out........ when you decrease value : zoom in for example : region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 4.1; – Erhan Demirci Sep 24 '13 at 10:40
  • 1
    @MR.Mustafa: Its working, awesome! But I dint think solving the issue is enough. So please some one explain me how it works. Or through any links. Sorry if I am silly, I am a beginner. Pls support. Thank you – Siddarth Hailstorm Oct 31 '13 at 06:13
  • 1
    @Mustafa ... Thanks it's saved my day. – Vvk Nov 25 '15 at 10:14
134

Why so complicated?

MKCoordinateRegion coordinateRegionForCoordinates(CLLocationCoordinate2D *coords, NSUInteger coordCount) {
    MKMapRect r = MKMapRectNull;
    for (NSUInteger i=0; i < coordCount; ++i) {
        MKMapPoint p = MKMapPointForCoordinate(coords[i]);
        r = MKMapRectUnion(r, MKMapRectMake(p.x, p.y, 0, 0));
    }
    return MKCoordinateRegionForMapRect(r);
}
me2
  • 3,713
  • 4
  • 14
  • 15
  • 6
    unbelievable how much simpler, cleaner, and easier this is than the posted alternatives. in fact, you can simplify this even further because there's no need to convert to MKCoordinateRegion – just call setVisibleMapRect: on your MKMapView with the MKMapRect that you create here. – lensovet Jan 26 '13 at 09:51
  • 2
    The annotations are sometimes stuck to the top of the map and not visible. Any input on the best approach to increase the zoom after the MKCoordinateRegion is created? – Kyle C Jan 16 '14 at 20:00
  • 3
    @KyleC `[mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(20.0f, 20.0f, 20.0f, 20.0f) animated:animated]; ` – user Feb 18 '14 at 21:04
  • How do you create the `CLLocationCoordinate2D *coords` array? Using `malloc()`? – Hlung Feb 23 '14 at 09:24
  • 4
    @KyleC. I added this to before returning `r` which basically zoom out 20 percent `CGFloat zoomOutPercent = 0.2f; r = MKMapRectMake(r.origin.x-r.size.width*zoomOutPercent, r.origin.y-r.size.height*zoomOutPercent, r.size.width*(1+zoomOutPercent*2), r.size.height*(1+zoomOutPercent*2));` – Loozie Jun 27 '14 at 22:07
  • FWIW, this code also works for WatchKit's `WKInterfaceMap`. If some of the pins appear too close to the edges and are barely visible, use @KyleC's `MKMapRect` adjustment in the comment above, as unlike with the regular iOS `MKMapView`, there is no `edgePadding` parameter for `WKInterfaceMap`'s `setVisibleMapRect` function. – Romain Dec 31 '14 at 08:33
  • I made this Swift Version: func coordinateRegion(for coordinates: [CLLocationCoordinate2D]) -> MKCoordinateRegion { var region: MKMapRect = MKMapRectNull for coord in coordinates { let point = MKMapPointForCoordinate(coord) region = MKMapRectUnion(region, MKMapRectMake(point.x, point.y, 0, 0)) } return MKCoordinateRegionForMapRect(region) } – Kevin Furman Mar 07 '19 at 20:26
51

As of iOS7 you can use showAnnotations:animated:

[mapView showAnnotations:annotations animated:YES];
Code Commander
  • 14,451
  • 5
  • 54
  • 63
45

I have done something similiar to this to zoom out (or in) to an area that included a point annotation and the current location. You could expand this by looping through your annotations.

The basic steps are:

  • Calculate the min lat/long
  • Calculate the max lat/long
  • Create CLLocation objects for these two points
  • Calculate distance between points
  • Create region using center point between points and distance converted to degrees
  • Pass region into MapView to adjust
  • Use adjusted region to set MapView region
    -(IBAction)zoomOut:(id)sender {

        CLLocationCoordinate2D southWest = _newLocation.coordinate;
        CLLocationCoordinate2D northEast = southWest;

        southWest.latitude = MIN(southWest.latitude, _annotation.coordinate.latitude);
        southWest.longitude = MIN(southWest.longitude, _annotation.coordinate.longitude);

        northEast.latitude = MAX(northEast.latitude, _annotation.coordinate.latitude);
        northEast.longitude = MAX(northEast.longitude, _annotation.coordinate.longitude);

        CLLocation *locSouthWest = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
        CLLocation *locNorthEast = [[CLLocation alloc] initWithLatitude:northEast.latitude longitude:northEast.longitude];

        // This is a diag distance (if you wanted tighter you could do NE-NW or NE-SE)
        CLLocationDistance meters = [locSouthWest getDistanceFrom:locNorthEast];

        MKCoordinateRegion region;
        region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0;
        region.center.longitude = (southWest.longitude + northEast.longitude) / 2.0;
        region.span.latitudeDelta = meters / 111319.5;
        region.span.longitudeDelta = 0.0;

        _savedRegion = [_mapView regionThatFits:region];
        [_mapView setRegion:_savedRegion animated:YES];

        [locSouthWest release];
        [locNorthEast release];
    }
Donald Byrd
  • 7,298
  • 3
  • 31
  • 49
  • This looks like the way to go. Thanks! – jbrennan Aug 27 '09 at 22:45
  • 1
    Managed to get this to work using `MKCoordinateRegionMake`: https://gist.github.com/1599700 in case anyone still wanna do it this way. – chakrit Jan 12 '12 at 10:15
  • region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0; Thanks for it – Tony Nov 07 '13 at 04:27
  • Does this work with points on either side of the meridian? The equator? – Eliot Dec 02 '13 at 01:50
  • 1
    This code places the locations offscreen when the locations have a similar y-value. Example, showing two locations at (50, -4) & (100, -3) will zoom the map too far, placing the coordinates off the left and right side of the screen. – user Feb 19 '14 at 21:51
  • As of iOS7 you can use [showAnnotations:animated:](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html#//apple_ref/occ/instm/MKMapView/showAnnotations:animated:) – Code Commander Mar 22 '14 at 01:27
21

I have a different answer. I was going to do implement the zoom-to-fit algorithm myself, but i figured that Apple must have a way to do what we wanted without so much work. Using the API doco quickly showed that I could use MKPolygon to do what was needed:

/* this simply adds a single pin and zooms in on it nicely */
- (void) zoomToAnnotation:(MapAnnotation*)annotation {
    MKCoordinateSpan span = {0.027, 0.027};
    MKCoordinateRegion region = {[annotation coordinate], span};
    [mapView setRegion:region animated:YES];
}

/* This returns a rectangle bounding all of the pins within the supplied
   array */
- (MKMapRect) getMapRectUsingAnnotations:(NSArray*)theAnnotations {
    MKMapPoint points[[theAnnotations count]];

    for (int i = 0; i < [theAnnotations count]; i++) {
        MapAnnotation *annotation = [theAnnotations objectAtIndex:i];
        points[i] = MKMapPointForCoordinate(annotation.coordinate);
    }

    MKPolygon *poly = [MKPolygon polygonWithPoints:points count:[theAnnotations count]];

    return [poly boundingMapRect];
}

/* this adds the provided annotation to the mapview object, zooming 
   as appropriate */
- (void) addMapAnnotationToMapView:(MapAnnotation*)annotation {
    if ([annotations count] == 1) {
        // If there is only one annotation then zoom into it.
        [self zoomToAnnotation:annotation];
    } else {
        // If there are several, then the default behaviour is to show all of them
        //
        MKCoordinateRegion region = MKCoordinateRegionForMapRect([self getMapRectUsingAnnotations:annotations]);

        if (region.span.latitudeDelta < 0.027) {
            region.span.latitudeDelta = 0.027;
        }

        if (region.span.longitudeDelta < 0.027) {
            region.span.longitudeDelta = 0.027;
        }
        [mapView setRegion:region];
    }

    [mapView addAnnotation:annotation];
    [mapView selectAnnotation:annotation animated:YES];
}

Hope this helps.

PKCLsoft
  • 1,349
  • 2
  • 25
  • 34
  • No problems. There is usually a better way if you're willing and have the time to spend on it. – PKCLsoft Sep 14 '12 at 06:50
  • I found this does put pins a little too close to the edge of the screen. Try adding annotationsRegion.span.latitudeDelta = annotationsRegion.span.latitudeDelta * kEventMapDetailBorderFactor; just before the setRegion. – Adam Eberbach Dec 10 '12 at 01:00
  • You're right @AdamEberbach, but it seems that your clip includes a constant that isn't available. Did you find a value that provided a "nice" border around the pins? – PKCLsoft May 07 '13 at 03:46
  • Code Commander's answer below about using the new showAnnotations method with iOS7 adds a nice margin, which actually work better, though this code is cooler. – James Toomey Jul 03 '14 at 20:56
14

you can also do it this way..

// Position the map so that all overlays and annotations are visible on screen.
MKMapRect regionToDisplay = [self mapRectForAnnotations:annotationsToDisplay];
if (!MKMapRectIsNull(regionToDisplay)) myMapView.visibleMapRect = regionToDisplay;

- (MKMapRect) mapRectForAnnotations:(NSArray*)annotationsArray
{
    MKMapRect mapRect = MKMapRectNull;

    //annotations is an array with all the annotations I want to display on the map
    for (id<MKAnnotation> annotation in annotations) { 

        MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
        MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);

        if (MKMapRectIsNull(mapRect)) 
        {
            mapRect = pointRect;
        } else 
        {
            mapRect = MKMapRectUnion(mapRect, pointRect);
        }
    }

     return mapRect;
}
Oliver
  • 21,876
  • 32
  • 134
  • 229
Manish Ahuja
  • 4,459
  • 3
  • 25
  • 35
13

Based on the information and suggestions from everyone I came up with the following. Thanks for everyone in this discussion for contributing :) This would go in the view Controller that contains the mapView.

- (void)zoomToFitMapAnnotations { 

if ([self.mapView.annotations count] == 0) return; 

int i = 0;
MKMapPoint points[[self.mapView.annotations count]];

//build array of annotation points
for (id<MKAnnotation> annotation in [self.mapView annotations])
        points[i++] = MKMapPointForCoordinate(annotation.coordinate);

MKPolygon *poly = [MKPolygon polygonWithPoints:points count:i];

[self.mapView setRegion:MKCoordinateRegionForMapRect([poly boundingMapRect]) animated:YES]; 
}
nh32rg
  • 1,412
  • 13
  • 15
7

Using Swift, a polygon, and some extra padding I used the following:

func zoomToFit() {
    var allLocations:[CLLocationCoordinate2D] = [
        CLLocationCoordinate2D(latitude: 32.768805, longitude: -117.167119),
        CLLocationCoordinate2D(latitude: 32.770480, longitude: -117.148385),
        CLLocationCoordinate2D(latitude: 32.869675, longitude: -117.212929)
    ]

    var poly:MKPolygon = MKPolygon(coordinates: &allLocations, count: allLocations.count)

    self.mapView.setVisibleMapRect(poly.boundingMapRect, edgePadding: UIEdgeInsetsMake(40.0, 40.0, 40.0, 40.0), animated: false)
}

5

Here is the SWIFT Equivalent (Confirmed Working in : Xcode6.1, SDK 8.2) for Mustafa's Answers:

func zoomToFitMapAnnotations() {
    if self.annotations.count == 0 {return}

    var topLeftCoordinate = CLLocationCoordinate2D(latitude: -90, longitude: 180)
    var bottomRightCoordinate = CLLocationCoordinate2D(latitude: 90, longitude: -180)

    for object in self.annotations {
        if let annotation = object as? MKAnnotation {
            topLeftCoordinate.longitude = fmin(topLeftCoordinate.longitude, annotation.coordinate.longitude)
            topLeftCoordinate.latitude = fmax(topLeftCoordinate.latitude, annotation.coordinate.latitude)
            bottomRightCoordinate.longitude = fmax(bottomRightCoordinate.longitude, annotation.coordinate.longitude)
            bottomRightCoordinate.latitude = fmin(bottomRightCoordinate.latitude, annotation.coordinate.latitude)
        }
    }

    let center = CLLocationCoordinate2D(latitude: topLeftCoordinate.latitude - (topLeftCoordinate.latitude - bottomRightCoordinate.latitude) * 0.5, longitude: topLeftCoordinate.longitude - (topLeftCoordinate.longitude - bottomRightCoordinate.longitude) * 0.5)

    print("\ncenter:\(center.latitude) \(center.longitude)")
    // Add a little extra space on the sides
    let span = MKCoordinateSpanMake(fabs(topLeftCoordinate.latitude - bottomRightCoordinate.latitude) * 1.01, fabs(bottomRightCoordinate.longitude - topLeftCoordinate.longitude) * 1.01)
    print("\nspan:\(span.latitudeDelta) \(span.longitudeDelta)")

    var region = MKCoordinateRegion(center: center, span: span)


    region = self.regionThatFits(region)

    self.setRegion(region, animated: true)

}
Niko
  • 3,277
  • 22
  • 34
Saru
  • 833
  • 1
  • 14
  • 22
  • 1
    Hey iOS_Developer. Thanks for the Swift conversion. For me it is not working because I think you are missing two "fmax" instead of the "fmin" for the topLeftCoordinate.latitude and bottomRightCoordinate.longitude. – Philipp Otto Oct 10 '16 at 09:41
5

In my case, I am starting with CLLocation objects and creating annotations for each of them.
I only need to place two annotations, so I have a simple approach to building the array of points, but it could easily be expanded to build an array with an arbitrary length given a set of CLLocations.

Here's my implementation (doesn't require creating MKMapPoints):

//start with a couple of locations
CLLocation *storeLocation = store.address.location.clLocation;
CLLocation *userLocation = [LBLocationController sharedController].currentLocation;

//build an array of points however you want
CLLocationCoordinate2D points[2] = {storeLocation.coordinate, userLocation.coordinate};

//the magic part
MKPolygon *poly = [MKPolygon polygonWithCoordinates:points count:2];
[self.mapView setRegion:MKCoordinateRegionForMapRect([poly boundingMapRect])];
jacobsimeon
  • 1,774
  • 1
  • 16
  • 20
4

I know this is an old question but, if you want to display all the annotations ALREADY ON the map use this:

 mapView.showAnnotations(mapView.annotations, animated: true)
Paul Lehn
  • 2,695
  • 20
  • 24
3

There's a new method in 'MKMapView' as of iOS 7 that you can use

Declaration

SWIFT

func showAnnotations(_ annotations: [AnyObject]!,
            animated animated: Bool)

OBJECTIVE-C

- (void)showAnnotations:(NSArray *)annotations
               animated:(BOOL)animated

Parameters

annotations The annotations that you want to be visible in the map. animated YES if you want the map region change to be animated, or NO if you want the map to display the new region immediately without animations.

Discussion

Calling this method updates the value in the region property and potentially other properties to reflect the new map region.

Community
  • 1
  • 1
Matt
  • 1,571
  • 14
  • 14
2

One possible solution might be measuring the distance between the current location and all the annotations and using the MKCoordinateRegionMakeWithDistance method to make a region that has a slightly greater distance than the furthest annotation.

This would of course get slower the more annotations you added though.

criscokid
  • 640
  • 1
  • 4
  • 11
  • I was going through the comments section only to validate myself. Glad that someone else thinks the way I did :-) Since I add only two annotations (begin and end point), I didn't feel any slowness. – thandasoru May 09 '13 at 16:43
2
- (void)zoomToFitMapAnnotations {

if ([self.mapview.annotations count] == 0) return;

int i = 0;
MKMapPoint points[[self.mapview.annotations count]];

//build array of annotation points
for (id<MKAnnotation> annotation in [self.mapview annotations])
    points[i++] = MKMapPointForCoordinate(annotation.coordinate);

MKPolygon *poly = [MKPolygon polygonWithPoints:points count:i];

[self.mapview setRegion:MKCoordinateRegionForMapRect([poly boundingMapRect]) animated:YES];
}
2

Based on the excellent answer by me2 (now in Swift)

func coordinateRegionForCoordinates(coords: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
    var rect: MKMapRect = MKMapRectNull
    for coord in coords {
        let point: MKMapPoint = MKMapPointForCoordinate(coord)
        rect = MKMapRectUnion(rect, MKMapRectMake(point.x, point.y, 0, 0))
    }
    return MKCoordinateRegionForMapRect(rect)
}
tilo
  • 13,266
  • 6
  • 62
  • 81
2

Adding additional to Stéphane de Luca's answer. Here we can keep UserLocation plus Custom annotations to fit MKMapView

 private func centerViewOnUserLocation() {
    
    if selectedLatitudeFromPreviousVC?.description != nil && selectedLongitudeFromPreviousVC?.description != nil {
        
        if let location = locationManager.location?.coordinate {
            let region = regionFor(coordinates: [
                CLLocationCoordinate2D(latitude: selectedLatitudeFromPreviousVC!, longitude: selectedLongitudeFromPreviousVC!),
                location])
            mkmapView.setRegion(region, animated: true)
        }
    } else {
        if let location = locationManager.location?.coordinate {
            let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
            mkmapView.setRegion(region, animated: true)
        }
    }
}

private func regionFor(coordinates coords: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
    var r = MKMapRect.null

    for i in 0 ..< coords.count {
        let p = MKMapPoint(coords[i])
        r = r.union(MKMapRect(x: p.x, y: p.y, width: 0, height: 0))
    }

    var coordinateRegion = MKCoordinateRegion(r)
    coordinateRegion.span.latitudeDelta *= 1.5
    coordinateRegion.span.longitudeDelta *= 1.5
    return coordinateRegion
}

enter image description here

Sreekanth G
  • 492
  • 4
  • 10
1

this code works for me, it shows all the pins with current location, hope this helps you,

func setCenterForMap() {
    var mapRect: MKMapRect = MKMapRectNull
    for loc in mapView.annotations {
        let point: MKMapPoint = MKMapPointForCoordinate(loc.coordinate)
        print( "location is : \(loc.coordinate)");
        mapRect = MKMapRectUnion(mapRect, MKMapRectMake(point.x,point.y,0,0))
    }
    if (locationManager.location != nil) {
        let point: MKMapPoint = MKMapPointForCoordinate(locationManager.location!.coordinate)
        print( "Cur location is : \(locationManager.location!.coordinate)");
        mapRect = MKMapRectUnion(mapRect, MKMapRectMake(point.x,point.y,0,0))
    }

    mapView.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsetsMake(40.0, 40.0, 40.0, 40.0), animated: true)

}
Patel Jigar
  • 1,971
  • 1
  • 21
  • 30
1

Added a little if clause to handle 1 location- to add to mustufa's cound code snippet. Used pkclSoft's zoomToAnnotation function for that:

if ([mapView.annotations count] == 1){
    MKCoordinateSpan span = {0.027, 0.027};
    region.span = span;
    CLLocationCoordinate2D singleCoordinate = [[mapView.annotations objectAtIndex:0] coordinate];
    region.center.latitude = singleCoordinate.latitude;
    region.center.longitude = singleCoordinate.longitude;
}
else
{
    // mustufa's code
}
Michael Reed
  • 2,352
  • 22
  • 15
0
CLLocationCoordinate2D min = CLLocationCoordinate2DMake(99999.0, 99999.0);
CLLocationCoordinate2D max = CLLocationCoordinate2DMake(-99999.0, -99999.0);

// find max/min....

// zoom to cover area
// TODO: Maybe better using a MKPolygon which can calculate its own fitting region.
CLLocationCoordinate2D center = CLLocationCoordinate2DMake((max.latitude + min.latitude) / 2.0, (max.longitude + min.longitude) / 2.0);
MKCoordinateSpan span = MKCoordinateSpanMake(max.latitude - min.latitude, max.longitude - min.longitude);
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);

[_mapView setRegion:[_mapView regionThatFits:region] animated:YES];
VSN
  • 2,311
  • 19
  • 29
0

Based on me2 response I wrote a category for MKMapView to add some margins and skip user location annotation:

@interface MKMapView (ZoomToFitAnnotations)
- (void)zoomToFitAnnotations:(BOOL)animated;
@end

@implementation MKMapView (ZoomToFitAnnotations)
- (void)zoomToFitAnnotations:(BOOL)animated {
    if (self.annotations.count == 0)
        return;

    MKMapRect rect = MKMapRectNull;
    for (id<MKAnnotation> annotation in self.annotations) {
        if ([annotation isKindOfClass:[MKUserLocation class]] == false) {
            MKMapPoint point = MKMapPointForCoordinate(annotation.coordinate);
            rect = MKMapRectUnion(rect, MKMapRectMake(point.x, point.y, 0, 0));
        }
    }

    MKCoordinateRegion region = MKCoordinateRegionForMapRect(rect);
    region.span.longitudeDelta *= 2; // Margin
    region.span.latitudeDelta *= 2; // Margin
    [self setRegion:region animated:animated];
}
@end
Tomasz
  • 3,416
  • 1
  • 19
  • 13
0

Since I can't comment on an answer, I would like to add my bit of convenience into @me2 's answer (since i thought it was the most elegant approach Found here).

For my personal project I simply added a category on the MKMapView class to encapsulate the "visible area" functionality for a ver common operation: setting to be able to see all the currently-loaded annotations on the MKMapView instance. the result was this:

.h file

#import <MapKit/MapKit.h>

@interface MKMapView (Extensions)

-(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated;
-(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated;


@end

.m file

#import "MKMapView+Extensions.h"

@implementation MKMapView (Extensions)

/**
 *  Changes the currently visible portion of the map to a region that best fits all the currently loadded annotations on the map, and it optionally animates the change.
 *
 *  @param animated is the change should be perfomed with an animation.
 */
-(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated
{
    MKMapView * mapView = self;

    NSArray * annotations = mapView.annotations;

    [self ij_setVisibleRectToFitAnnotations:annotations animated:animated];

}


/**
 *  Changes the currently visible portion of the map to a region that best fits the provided annotations array, and it optionally animates the change.
    All elements from the array must conform to the <MKAnnotation> protocol in order to fetch the coordinates to compute the visible region of the map.
 *
 *  @param annotations an array of elements conforming to the <MKAnnotation> protocol, holding the locations for which the visible portion of the map will be set.
 *  @param animated    wether or not the change should be perfomed with an animation.
 */
-(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated
{
    MKMapView * mapView = self;

    MKMapRect r = MKMapRectNull;
    for (id<MKAnnotation> a in annotations) {
        ZAssert([a conformsToProtocol:@protocol(MKAnnotation)], @"ERROR: All elements of the array MUST conform to the MKAnnotation protocol. Element (%@) did not fulfill this requirement", a);
        MKMapPoint p = MKMapPointForCoordinate(a.coordinate);
        //MKMapRectUnion performs the union between 2 rects, returning a bigger rect containing both (or just one if the other is null). here we do it for rects without a size (points)
        r = MKMapRectUnion(r, MKMapRectMake(p.x, p.y, 0, 0));
    }

    [mapView setVisibleMapRect:r animated:animated];

}

@end

As you can see, I've added 2 methods so far: one for setting the visible region of the map to the one that fits all currently-loaded annotations on the MKMapView instance, and another method to set it to any array of objects. So to set the mapView's visible region the code would then be as simple as:

   //the mapView instance  
    [self.mapView ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:animated]; 

I hope it helps =)

Robertibiris
  • 794
  • 6
  • 15
0

Consider this extension:

extension MKCoordinateRegion {
    init(locations: [CLLocationCoordinate2D], marginMultiplier: Double = 1.1) {
        let mapRect = locations.reduce(MKMapRect(), {
            let point = MKMapPointForCoordinate($1)
            let rect = MKMapRect(origin: point, size: MKMapSize(width: 0.0, height: 0.0))
            return MKMapRectUnion($0, rect)
        })

        var coordinateRegion = MKCoordinateRegionForMapRect(mapRect)
        coordinateRegion.span.latitudeDelta *= marginMultiplier
        coordinateRegion.span.longitudeDelta *= marginMultiplier
        self = coordinateRegion
    }
}
nsmeme
  • 211
  • 4
  • 1
0

A swift 5 version:

   func regionFor(coordinates coords: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
        var r = MKMapRect.null

        for i in 0 ..< coords.count {
            let p = MKMapPoint(coords[i])

            r = r.union(MKMapRect(x: p.x, y: p.y, width: 0, height: 0))
        }

        return MKCoordinateRegion(r)
    }
Stéphane de Luca
  • 10,239
  • 7
  • 45
  • 74
0

I hope this is at least relevant, this is what I put together for Mono (based off of pkclSoft's answer):

void ZoomMap (MKMapView map)
{
    var annotations = map.Annotations;

    if (annotations == null || annotations.Length == 0) 
        return;

    var points = annotations.OfType<MapAnnotation> ()
                            .Select (s => MKMapPoint.FromCoordinate (s.Coordinate))
                            .ToArray ();            

    map.SetVisibleMapRect(MKPolygon.FromPoints (points).BoundingMapRect, true); 
}
nullable
  • 2,133
  • 1
  • 24
  • 29