201

I am using MKMapView and have added a number of annotation pins to the map about a 5-10 kilometre area. When I run the application my map starts zoomed out to show the whole world, what is the best way to zoom the map so the pins fit the view?

EDIT: My initial thinking would be to use MKCoordinateRegionMake and calculate the coordinate centre, longitudeDelta and latitudeDelta from my annotations. I am pretty sure this will work, but I just wanted to check I was not missing anything obvious.

Code added, BTW: FGLocation is an class that conforms to MKAnnotation, locationFake is an NSMutableArray of these objects. Comments are always welcome ....

- (MKCoordinateRegion)regionFromLocations {
    CLLocationCoordinate2D upper = [[locationFake objectAtIndex:0] coordinate];
    CLLocationCoordinate2D lower = [[locationFake objectAtIndex:0] coordinate];

    // FIND LIMITS
    for(FGLocation *eachLocation in locationFake) {
        if([eachLocation coordinate].latitude > upper.latitude) upper.latitude = [eachLocation coordinate].latitude;
        if([eachLocation coordinate].latitude < lower.latitude) lower.latitude = [eachLocation coordinate].latitude;
        if([eachLocation coordinate].longitude > upper.longitude) upper.longitude = [eachLocation coordinate].longitude;
        if([eachLocation coordinate].longitude < lower.longitude) lower.longitude = [eachLocation coordinate].longitude;
    }

    // FIND REGION
    MKCoordinateSpan locationSpan;
    locationSpan.latitudeDelta = upper.latitude - lower.latitude;
    locationSpan.longitudeDelta = upper.longitude - lower.longitude;
    CLLocationCoordinate2D locationCenter;
    locationCenter.latitude = (upper.latitude + lower.latitude) / 2;
    locationCenter.longitude = (upper.longitude + lower.longitude) / 2;

    MKCoordinateRegion region = MKCoordinateRegionMake(locationCenter, locationSpan);
    return region;
}
TheNeil
  • 3,321
  • 2
  • 27
  • 52
fuzzygoat
  • 26,573
  • 48
  • 165
  • 294
  • 10
    iOS 7 note: The new [showAnnotations:animated:](https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html#//apple_ref/doc/uid/TP40008205-CH3-SW83) method can help you avoid this manual region calculation. –  Oct 16 '13 at 01:12

26 Answers26

340

This is the one I found here that worked for me:

(EDIT: I have updated the solution using @Micah's suggestion to increase the pointRect by 0.1 to ensure the rect doesn't end up being infinitesimally small!)

MKMapRect zoomRect = MKMapRectNull;
for (id <MKAnnotation> annotation in mapView.annotations)
{
    MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
    MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
    zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
[mapView setVisibleMapRect:zoomRect animated:YES];

 

You could also update this to include the userLocation pin by replacing the first line with:

MKMapPoint annotationPoint = MKMapPointForCoordinate(mapView.userLocation.coordinate);
MKMapRect zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
Tom McKenzie
  • 742
  • 8
  • 19
jowie
  • 8,028
  • 8
  • 55
  • 94
  • 4
    Nice indeed. You do not need to check isNull though. MKMapRectUnion does this for you. From the docs: "If either rectangle is null, this method returns the other rectangle." – Felix Lamouroux Jan 24 '13 at 16:41
  • 41
    Very nice solution!!! Here's an extra little touch to add some padding: double inset = -zoomRect.size.width * 0.1; [self.mapView setVisibleMapRect:MKMapRectInset(zoomRect, inset, inset) animated:YES]; – Craig B Aug 05 '13 at 00:03
  • 1
    Awesome! Potential addition: if you want to exclude the 'current location annotation' just add a if statement in the for loop: if (![annotation isKindOfClass:[MKUserLocation class]]) { // Do stuff here } – kgaidis Nov 04 '14 at 21:22
  • 2
    @CraigB solution for padding is excellent, but it doesn't work well when path is vertical, e.g movement from south to north, to fix this use double inset = MIN(-zoomRect.size.width * 0.1, -zoomRect.size.height * 0.1); – Farhad Malekpour Feb 01 '15 at 00:24
  • 1
    Enhancement with padding : double insetWidth = -zoomRect.size.width * 0.2; double insetHeight = -zoomRect.size.height * 0.2; MKMapRect insetRect = MKMapRectInset(zoomRect, insetWidth, insetHeight); Then use this new insertRect – Dulgan Mar 06 '15 at 10:59
  • Seems not to work if one location in USA another in Russia I could fit it by myself, but not with this algorithm – Olexiy Pyvovarov Jan 30 '19 at 19:46
124

Apple has added a new method for IOS 7 to simplify life a bit.

[mapView showAnnotations:yourAnnotationArray animated:YES];

You can easily pull from an array stored in the map view:

yourAnnotationArray = mapView.annotations;

and quickly adjust the camera too!

mapView.camera.altitude *= 1.4;

this won't work unless the user has iOS 7+ or OS X 10.9+ installed. check out custom animation here

Community
  • 1
  • 1
Ryan Berg
  • 1,761
  • 1
  • 13
  • 9
  • I'm not sure if this is because of some other factors in my implementation, but I find that `showAnnotations` doesn't do as close a zoom/fit of the annotations as the manual implementation does, so I've stuck with the manual one. – Ted Avery Apr 17 '14 at 00:35
  • 1
    try multipling the cameras altitude by a fraction of one, like mapView.camera.altitude *= .85; for a closer viewport – Ryan Berg May 30 '14 at 02:47
  • I've also found this useful for selecting annotations outside the current visible map area. By default, MapView doesn't select non-visible annotations. Call showAnnotations with an array of your non-visible annotation(s), prior to calling selectAnnotation, and the map should update its viewable area. – MandisaW Mar 07 '16 at 17:32
124

You've got it right.

Find your maximum and minimum latitudes and longitudes, apply some simple arithmetic, and use MKCoordinateRegionMake.

For iOS 7 and above, use showAnnotations:animated:, from MKMapView.h:

// Position the map such that the provided array of annotations are all visible to the fullest extent possible. 
- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);
pkamb
  • 33,281
  • 23
  • 160
  • 191
Matthew Frederick
  • 22,245
  • 10
  • 71
  • 97
  • 159
    **For iOS 7 and above (Referring MKMapView.h) :** `// Position the map such that the provided array of annotations are all visible to the fullest extent possible.` `- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);` – Abhishek Bedi Jan 30 '14 at 20:01
  • 1
    This works well but occasionally when I zoom in (in the map) then try to center it (using a button that invokes this method) it doesnt seem to work. – RPM Jul 10 '15 at 19:20
  • 5
    It's important to note that `showAnnotations` also adds the annotations to the map, even if an annotation for that location already exists. – Eneko Alonso Feb 29 '16 at 22:26
  • @EnekoAlonso You can work around this by calling `removeAnnotations(_ annotations:)` immediately after `showAnnotations(_ annotations:animated)` – Alain Stulz Aug 18 '16 at 19:46
  • 1
    Also worth noting is that while showAnnotations sets the region to display annotations, the region still gets adjusted to match the aspect ratio; and this will frequently exclude some of the annotations. Also note that showAnnotations is the only correct solution presented here; none of the other answers even attempt to handle annotations spanning the international date line. – Gordon Dove Sep 23 '16 at 11:21
42

I use this this code and works fine for me:

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

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

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

    for(MapViewAnnotation *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;
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1; // Add a little extra space on the sides
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; // Add a little extra space on the sides

    region = [aMapView regionThatFits:region];
    [mapView setRegion:region animated:YES];
}
Rafael Moreira
  • 1,759
  • 2
  • 19
  • 20
  • Do not work for: ▿ 2 elements ▿ 0 : CLLocationCoordinate2D - latitude : 46.969995730376894 - longitude : -109.2494943434474 ▿ 1 : CLLocationCoordinate2D - latitude : 63.23212154333072 - longitude : 174.13666611126533 – Olexiy Pyvovarov Jan 30 '19 at 20:01
28

In Swift use

mapView.showAnnotations(annotationArray, animated: true)

In Objective c

[mapView showAnnotations:annotationArray animated:YES];
Sreedeepkesav M S
  • 1,165
  • 14
  • 16
  • 5
    If the annotations have already been set on the mapView, you can reference them directly with: `mapView.showAnnotations(mapView.annotations, animated: true)` – Justin Vallely Nov 06 '18 at 15:17
16

I have converted the answer by Rafael Moreira. The credit goes to him. For those of you looking for the Swift version, here is the code:

 func zoomToFitMapAnnotations(aMapView: MKMapView) {
    guard aMapView.annotations.count > 0 else {
        return
    }
    var topLeftCoord: CLLocationCoordinate2D = CLLocationCoordinate2D()
    topLeftCoord.latitude = -90
    topLeftCoord.longitude = 180
    var bottomRightCoord: CLLocationCoordinate2D = CLLocationCoordinate2D()
    bottomRightCoord.latitude = 90
    bottomRightCoord.longitude = -180
    for annotation: MKAnnotation in myMap.annotations as! [MKAnnotation]{
        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)
    }

    var region: MKCoordinateRegion = MKCoordinateRegion()
    region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5
    region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.4
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.4
    region = aMapView.regionThatFits(region)
    myMap.setRegion(region, animated: true)
}
YanSte
  • 10,661
  • 3
  • 57
  • 53
Prashant Khanal
  • 249
  • 2
  • 11
16

I created an extension to show all the annotations using some code from here and there in swift. This will not show all annotations if they can't be shown even at maximum zoom level.

import MapKit

extension MKMapView {
    func fitAllAnnotations() {
        var zoomRect = MKMapRectNull;
        for annotation in annotations {
            let annotationPoint = MKMapPointForCoordinate(annotation.coordinate)
            let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
            zoomRect = MKMapRectUnion(zoomRect, pointRect);
        }
        setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50), animated: true)
    }
}
Tripti Kumar
  • 1,559
  • 14
  • 28
Ankit Srivastava
  • 12,347
  • 11
  • 63
  • 115
  • I managed to get better results by altering the `UIEdgeInsetsMake` parameters, values between 30 and 100 were good for me. I was testing using iPhone SE i)S 10.2 Simulator. Example code: `setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(100, 100, 100, 100), animated: true)`. As a note, This Code Works in Swift 3 and XCode 8.2.1. – nyxee Jun 07 '17 at 10:03
14

Swift 3 This is de correct way for fit all annotations in map.

func zoomMapaFitAnnotations() {

        var zoomRect = MKMapRectNull
        for annotation in mapview.annotations {

            let annotationPoint = MKMapPointForCoordinate(annotation.coordinate)

            let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0)

            if (MKMapRectIsNull(zoomRect)) {
                zoomRect = pointRect
            } else {
                zoomRect = MKMapRectUnion(zoomRect, pointRect)
            }
        }
        self.mapview.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(50, 50, 50, 50), animated: true)

    }
oscar castellon
  • 3,048
  • 30
  • 19
  • @ArshadShaik Your [suggested edit](https://stackoverflow.com/review/suggested-edits/23754211) has been rejected, if you want to provide a new answer for Swift 4.2, feel free, but add it as an answer, not by editing it into another users post. – Nick is tired Aug 09 '19 at 15:53
13

@jowie's solution works great. One catch, if a map has only one annotation you'll end up with a fully zoomed out map. I added 0.1 to the rect make size to make sure setVisibleMapRect has something to zoom to.

MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
Micah
  • 131
  • 1
  • 3
12

If your are looking for iOS 8 and above, the simplest way to do it is to set the var layoutMargins: UIEdgeInsets { get set } of your map view before calling func showAnnotations(annotations: [MKAnnotation], animated: Bool)

For instance (Swift 2.1):

@IBOutlet weak var map: MKMapView! {
    didSet {
        map.delegate = self
        map.mapType = .Standard
        map.pitchEnabled = false
        map.rotateEnabled = false
        map.scrollEnabled = true
        map.zoomEnabled = true
    }
}

// call 'updateView()' when viewWillAppear or whenever you set the map annotations
func updateView() {
    map.layoutMargins = UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25)
    map.showAnnotations(map.annotations, animated: true)
}
Henrik Perrochon
  • 347
  • 4
  • 11
8

For iOS 7 and above (Referring MKMapView.h) :

// Position the map such that the provided array of annotations are all visible to the fullest extent possible.          

- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);

remark from – Abhishek Bedi

You just call:

 [yourMapView showAnnotations:@[yourAnnotation] animated:YES];
Esha
  • 1,328
  • 13
  • 34
Seke1412
  • 81
  • 1
  • 2
  • Just for reference, the NS_AVAILABLE text was in there because in January 2011, having iOS 7 on a device wasn't super likely, and the NS_AVAILABLE protected the app from build failure or crash. – Matthew Frederick Oct 04 '17 at 19:14
8

Added this If loop within the for loop to exclude the users location pin from this method (required in my case, and maybe others)

if (![annotation isKindOfClass:[MKUserLocation class]] ) {

//Code Here...

}
Sammio2
  • 7,422
  • 7
  • 34
  • 49
6
    var zoomRect: MKMapRect = MKMapRect.null
    for annotation in mapView.annotations {
        let annotationPoint = MKMapPoint(annotation.coordinate)
        let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.1, height: 0.1)
        zoomRect = zoomRect.union(pointRect)
    }
    mapView.setVisibleMapRect(zoomRect, animated: true)

// Edited for swift 5

Tripti Kumar
  • 1,559
  • 14
  • 28
alicanbatur
  • 2,172
  • 1
  • 28
  • 36
5

In Swift

    var zoomRect = MKMapRectNull;

    for i in 0..<self.map.annotations.count {

        let annotation: MKAnnotation = self.map.annotations[i]

        let annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
        let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
        zoomRect = MKMapRectUnion(zoomRect, pointRect);
    }

    self.map.setVisibleMapRect(zoomRect, animated: true)
4

Thanks to jowie I've updated my old category to more elegant solution. Sharing complete, almost copy&paste ready solution

MKMapView+AnnotationsRegion.h

#import <MapKit/MapKit.h>

@interface MKMapView (AnnotationsRegion)

-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated;
-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding;

-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated;
-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding;

@end

MKMapView+AnnotationsRegion.m

#import "MKMapView+AnnotationsRegion.h"

@implementation MKMapView (AnnotationsRegion)

-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated{
    [self updateRegionForCurrentAnnotationsAnimated:animated edgePadding:UIEdgeInsetsZero];
}
-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding{
    [self updateRegionForAnnotations:self.annotations animated:animated edgePadding:edgePadding];
}

-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated{
    [self updateRegionForAnnotations:annotations animated:animated edgePadding:UIEdgeInsetsZero];
}
-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding{
    MKMapRect zoomRect = MKMapRectNull;
    for(id<MKAnnotation> annotation in annotations){
        MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
        MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
        zoomRect = MKMapRectUnion(zoomRect, pointRect);
    }
    [self setVisibleMapRect:zoomRect edgePadding:edgePadding animated:animated];
}

@end

Hope it helps someone and thanks again jowie!

JakubKnejzlik
  • 6,363
  • 3
  • 40
  • 41
4
 - (void)zoomMapViewToFitAnnotationsWithExtraZoomToAdjust:(double)extraZoom
{

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

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

   for (id<MKAnnotation> annotation in [self annotations])
  {
      points[i++] = MKMapPointForCoordinate(annotation.coordinate);
   }

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

MKCoordinateRegion r = MKCoordinateRegionForMapRect([poly boundingMapRect]);
r.span.latitudeDelta += extraZoom;
r.span.longitudeDelta += extraZoom;

[self setRegion: r animated:YES];

}
Ravi Karadbhajane
  • 123
  • 1
  • 1
  • 6
4

As Abhishek Bedi points out in a comment, For iOS7 forward the best way to do this is:

//from API docs: 
//- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);
[self.mapView showAnnotations:self.mapView.annotations animated:YES];

For my personal project (prior to iOS7) I simply added a category on the MKMapView class to encapsulate the "visible area" functionality for a very common operation: setting it to be able to see all the currently-loaded annotations on the MKMapView instance (this includes as many pins as you might have placed, as well as the user's location). 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
  • 844
  • 8
  • 15
3

All the answers on this page assume that the map occupies the full screen. I actually have a HUD display (ie buttons scattered at the top and bottom) that give information ontop of the map.. and so the algorithms on the page will display the pins all right, but some of them will appear under the HUD display buttons.

My solution zooms the map in to display the annotations in a subset of the screen and works for different screen sizes (ie 3.5" vs 4.0" etc):

// create a UIView placeholder and throw it on top of the original mapview
// position the UIView to fit the maximum area not hidden by the HUD display buttons
// add an *other* mapview in that uiview, 
// get the MKCoordinateRegion that fits the pins from that fake mapview
// kill the fake mapview and set the region of the original map 
// to that MKCoordinateRegion.

Here is what I did in code (note: i use NSConstraints with some helper methods to make my code work in different screen sizes.. while the code is quite readable.. my answer here explains it better.. it's basically the same workflow:)

// position smallerMap to fit available space
// don't store this map, it will slow down things if we keep it hidden or even in memory
[@[_smallerMapPlaceholder] mapObjectsApplyingBlock:^(UIView *view) {
    [view removeFromSuperview];
    [view setTranslatesAutoresizingMaskIntoConstraints:NO];
    [view setHidden:NO];
    [self.view addSubview:view];
}];

NSDictionary *buttonBindingDict = @{ @"mapPlaceholder": _smallerMapPlaceholder};

NSArray *constraints = [@[@"V:|-225-[mapPlaceholder(>=50)]-176-|",
                          @"|-40-[mapPlaceholder(<=240)]-40-|"
                          ] mapObjectsUsingBlock:^id(NSString *formatString, NSUInteger idx){
                              return [NSLayoutConstraint constraintsWithVisualFormat:formatString options:0 metrics:nil views:buttonBindingDict];
                          }];

[self.view addConstraints:[constraints flattenArray]];
[self.view layoutIfNeeded];

MKMapView *smallerMap = [[MKMapView alloc] initWithFrame:self.smallerMapPlaceholder.frame];
[_smallerMapPlaceholder addSubview:smallerMap];

MKCoordinateRegion regionThatFits = [smallerMap getRegionThatFits:self.mapView.annotations];
[smallerMap removeFromSuperview];
smallerMap = nil;
[_smallerMapPlaceholder setHidden:YES];

[self.mapView setRegion:regionThatFits animated:YES];

here is the code that gets region that fits:

- (MKCoordinateRegion)getRegionThatFits:(NSArray *)routes {
    MKCoordinateRegion region;
    CLLocationDegrees maxLat = -90.0;
    CLLocationDegrees maxLon = -180.0;
    CLLocationDegrees minLat = 90.0;
    CLLocationDegrees minLon = 180.0;
    for(int idx = 0; idx < routes.count; idx++)
    {
        CLLocation* currentLocation = [routes objectAtIndex:idx];
        if(currentLocation.coordinate.latitude > maxLat)
            maxLat = currentLocation.coordinate.latitude;
        if(currentLocation.coordinate.latitude < minLat)
            minLat = currentLocation.coordinate.latitude;
        if(currentLocation.coordinate.longitude > maxLon)
            maxLon = currentLocation.coordinate.longitude;
        if(currentLocation.coordinate.longitude < minLon)
            minLon = currentLocation.coordinate.longitude;
    }
    region.center.latitude     = (maxLat + minLat) / 2.0;
    region.center.longitude    = (maxLon + minLon) / 2.0;
    region.span.latitudeDelta = 0.01;
    region.span.longitudeDelta = 0.01;

    region.span.latitudeDelta  = ((maxLat - minLat)<0.0)?100.0:(maxLat - minLat);
    region.span.longitudeDelta = ((maxLon - minLon)<0.0)?100.0:(maxLon - minLon);

    MKCoordinateRegion regionThatFits = [self regionThatFits:region];
    return regionThatFits;
}
Community
  • 1
  • 1
abbood
  • 23,101
  • 16
  • 132
  • 246
3

Swift 5+ in 2021

// Position the map such that the provided array of annotations are all visible to the fullest extent possible.
    @available(iOS 7.0, *)
    open func showAnnotations(_ annotations: [MKAnnotation], animated: Bool)

so you can just:

 mapView.showAnnotations(mapView.annotations, animated: true)
Ahmadreza
  • 6,950
  • 5
  • 50
  • 69
2

I've made a little modification of Rafael's code for MKMapView Category.

- (void)zoomToFitMapAnnotations {
    if ([self.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 self.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;
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1; // Add a little extra space on the sides
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; // Add a little extra space on the sides

    [self setRegion:[self regionThatFits:region] animated:YES];
}
Kirow
  • 1,077
  • 12
  • 25
2

Based on answers above you can use universal method to zoom map to fit all annotations and overlays at the same time.

-(MKMapRect)getZoomingRectOnMap:(MKMapView*)map toFitAllOverlays:(BOOL)overlays andAnnotations:(BOOL)annotations includeUserLocation:(BOOL)userLocation {
    if (!map) {
        return MKMapRectNull;
    }

    NSMutableArray* overlaysAndAnnotationsCoordinateArray = [[NSMutableArray alloc]init];        
    if (overlays) {
        for (id <MKOverlay> overlay in map.overlays) {
            MKMapPoint overlayPoint = MKMapPointForCoordinate(overlay.coordinate);
            NSArray* coordinate = @[[NSNumber numberWithDouble:overlayPoint.x], [NSNumber numberWithDouble:overlayPoint.y]];
            [overlaysAndAnnotationsCoordinateArray addObject:coordinate];
        }
    }

    if (annotations) {
        for (id <MKAnnotation> annotation in map.annotations) {
            MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
            NSArray* coordinate = @[[NSNumber numberWithDouble:annotationPoint.x], [NSNumber numberWithDouble:annotationPoint.y]];
            [overlaysAndAnnotationsCoordinateArray addObject:coordinate];
        }
    }

    MKMapRect zoomRect = MKMapRectNull;
    if (userLocation) {
        MKMapPoint annotationPoint = MKMapPointForCoordinate(map.userLocation.coordinate);
        zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
    }

    for (NSArray* coordinate in overlaysAndAnnotationsCoordinateArray) {
        MKMapRect pointRect = MKMapRectMake([coordinate[0] doubleValue], [coordinate[1] doubleValue], 0.1, 0.1);
        zoomRect = MKMapRectUnion(zoomRect, pointRect);
    }

    return zoomRect;
}

And then:

MKMapRect mapRect = [self getZoomingRectOnMap:mapView toFitAllOverlays:YES andAnnotations:YES includeUserLocation:NO];
[mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0) animated:YES];
1

Just sharing my observations on this:

If you are using xCode > 6 with "inferred" sizes for the screens (see "simulated metrics" on the file inspector) in storyboard, calling

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

in viewDidLoad will result in a too large zoom level on iPhones with 4 inches because the layout for the map is still on the size of the wider screens from the storyboard.

You can move your call to showAnnotations... to viewDidAppear. Then the size of the map has already been adjusted to the smaller screen of an iPhone 4.

Or alternatively change the value "inferred" in the file inspector under "simulated metrics" to iphone 4-inch.

Jens
  • 6,243
  • 1
  • 49
  • 79
1

You can select which shapes you want to show along with the Annotations.

extension MKMapView {
  func setVisibleMapRectToFitAllAnnotations(animated: Bool = true,
                                            shouldIncludeUserAccuracyRange: Bool = true,
                                            shouldIncludeOverlays: Bool = true,
                                            edgePadding: UIEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)) {
    var mapOverlays = overlays

    if shouldIncludeUserAccuracyRange, let userLocation = userLocation.location {
      let userAccuracyRangeCircle = MKCircle(center: userLocation.coordinate, radius: userLocation.horizontalAccuracy)
      mapOverlays.append(MKOverlayRenderer(overlay: userAccuracyRangeCircle).overlay)
    }

    if shouldIncludeOverlays {
      let annotations = self.annotations.filter { !($0 is MKUserLocation) }
      annotations.forEach { annotation in
        let cirlce = MKCircle(center: annotation.coordinate, radius: 1)
        mapOverlays.append(cirlce)
      }
    }

    let zoomRect = MKMapRect(bounding: mapOverlays)
    setVisibleMapRect(zoomRect, edgePadding: edgePadding, animated: animated)
  }
}

extension MKMapRect {
  init(bounding overlays: [MKOverlay]) {
    self = .null
    overlays.forEach { overlay in
      let rect: MKMapRect = overlay.boundingMapRect
      self = self.union(rect)
    }
  }
}
Anirudha Mahale
  • 2,526
  • 3
  • 37
  • 57
0

Put this in to your code accordingly:

  - (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
    {
    id<MKAnnotation> mp = [annotationView annotation];
        MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate] ,250,250);

       [mv setRegion:region animated:YES];

}
Vinodh
  • 5,262
  • 4
  • 38
  • 68
Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62
0

@"I'm not sure if this is because of some other factors in my implementation, but I find that showAnnotations doesn't do as close a zoom/fit of the annotations as the manual implementation does, so I've stuck with the manual one. – Ted Avery Apr 17 at 0:35"

I had the same problem, but then I tried doing showAnnotations twice (like below), and for some reason, it worked.

[mapView showAnnotations:yourAnnotationArray animated:YES]; [mapView showAnnotations:yourAnnotationArray animated:YES];

zakton
  • 41
  • 1
  • 4
0

An iOS 7 compatible way is to use the following. First call showAnnotation in order to get a rectangle including all annotations. Afterwards create and UIEdgeInset with an top inset of the pin height. Thus you ensure to show the whole pin on the map.

[self.mapView showAnnotations:self.mapView.annotations animated:YES];
MKMapRect rect = [self.mapView visibleMapRect];
UIEdgeInsets insets = UIEdgeInsetsMake(pinHeight, 0, 0, 0);
[self.mapView setVisibleMapRect:rect edgePadding:insets animated:YES];
Morpheus78
  • 870
  • 1
  • 9
  • 21