Positioning MKMapView to show multiple annotations at once
Asked Answered
O

24

97

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?

Oligosaccharide answered 26/8, 2009 at 17:35 Comment(0)
P
53

As of iOS7 you can use showAnnotations:animated:

[mapView showAnnotations:annotations animated:YES];
Piemonte answered 22/3, 2014 at 1:27 Comment(0)
S
140

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);
}
Stebbins answered 8/8, 2012 at 10:41 Comment(7)
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.Natalya
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?Cholla
@KyleC [mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(20.0f, 20.0f, 20.0f, 20.0f) animated:animated]; Fled
How do you create the CLLocationCoordinate2D *coords array? Using malloc()?Karissakarita
@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));Shamble
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.Twist
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) }Tunable
R
138

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

- (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]; 
}
Rms answered 26/8, 2011 at 6:22 Comment(4)
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.Cherubini
Thank you very much. I added this to a subclass of MKMapView and changed the method to - (void) zoomToFitAnnotations:(BOOL)animated. Works perfectly!Uniformize
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;Mackinnon
@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 youJosselyn
P
53

As of iOS7 you can use showAnnotations:animated:

[mapView showAnnotations:annotations animated:YES];
Piemonte answered 22/3, 2014 at 1:27 Comment(0)
L
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];
    }
Lancey answered 27/8, 2009 at 19:56 Comment(6)
This looks like the way to go. Thanks!Oligosaccharide
Managed to get this to work using MKCoordinateRegionMake: gist.github.com/1599700 in case anyone still wanna do it this way.Katzman
region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0; Thanks for itJoell
Does this work with points on either side of the meridian? The equator?Cullen
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.Fled
As of iOS7 you can use showAnnotations:animated:Piemonte
W
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.

Wedurn answered 4/10, 2011 at 1:50 Comment(4)
No problems. There is usually a better way if you're willing and have the time to spend on it.Wedurn
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.Coley
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?Wedurn
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.Ganof
D
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;
}
Duwalt answered 12/9, 2011 at 4:59 Comment(0)
L
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]; 
}
Languet answered 20/10, 2011 at 20:0 Comment(1)
This should get more up votes. Very precise and to the point.Paperweight
F
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)
}

Frobisher answered 6/4, 2015 at 15:46 Comment(1)
setVisibleMapRect(...). I was doing the math myself... poorly.Sumter
O
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])];
Outbuilding answered 25/1, 2012 at 20:11 Comment(0)
J
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)

}
Jabber answered 23/1, 2015 at 11:19 Comment(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.Fathom
H
5

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)
Hwang answered 23/12, 2016 at 20:35 Comment(1)
This is valid, however, when one needs zoom out more, the animation will have to be disabled and used when setting the region. Things becomes a bit glitchy.Twentyfour
P
4

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.

Posture answered 26/2, 2015 at 4:39 Comment(0)
K
3

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

private func coordinateRegion(for coordinates: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
    var rect: MKMapRect = .null
    for coordinate in coordinates {
        let point: MKMapPoint = MKMapPoint(coordinate)
        rect = rect.union(MKMapRect(x: point.x, y: point.y, width: 0, height: 0))
    }
    return MKCoordinateRegion(rect)
}
Kristiekristien answered 18/5, 2015 at 14:5 Comment(0)
E
3

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

Electioneer answered 10/9, 2020 at 7:12 Comment(0)
M
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.

Mines answered 26/8, 2009 at 20:13 Comment(1)
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.Cazzie
R
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];
}
Red answered 3/12, 2014 at 9:56 Comment(0)
C
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
}
Cherubini answered 31/1, 2012 at 4:31 Comment(0)
D
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)

}
Dempsey answered 8/4, 2016 at 8:35 Comment(0)
A
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); 
}
Ahola answered 6/3, 2012 at 6:13 Comment(0)
Z
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];
Zaller answered 27/7, 2012 at 12:8 Comment(0)
S
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
Stylolite answered 15/4, 2014 at 5:39 Comment(0)
H
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 =)

Hollie answered 10/6, 2014 at 13:16 Comment(0)
A
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
    }
}
Abisia answered 30/9, 2017 at 13:21 Comment(0)
A
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)
    }
Arhat answered 28/8, 2019 at 9:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.