Zooming MKMapView to fit annotation pins? - ios

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;
}

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);

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);

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

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];
}

In Swift use
mapView.showAnnotations(annotationArray, animated: true)
In Objective c
[mapView showAnnotations:annotationArray animated:YES];

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)
}
}

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)
}

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)
}

#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);

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)
}

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...
}

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];

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

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)

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!

- (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];
}

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 =)

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;
}

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];
}

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];

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)

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.

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)
}
}
}

#"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];

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];

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];
}

Related

mkmapview show route and annotations in a specified frame

I am using below method for showing annotations and route in mkmapview but it is showing only annotations in my mkmapview not showing complete route in map view's boundary
Can any one suggest what modifications I need to do for showing everything properly.
Thanks
- (void)zoomAnnotationsOnMapView:(MKMapView *)mapView toFrame:(CGRect)annotationsFrame animated:(BOOL)animated
{
NSArray *annotations = mapView.annotations;
int count = (int)[mapView.annotations count];
if ( count == 0) { return; } //bail if no annotations
//convert NSArray of id <MKAnnotation> into an MKCoordinateRegion that can be used to set the map size
//can't use NSArray with MKMapPoint because MKMapPoint is not an id
MKMapPoint points[count]; //C array of MKMapPoint struct
for( int i=0; i<count; i++ ) //load points C array by converting coordinates to points
{
CLLocationCoordinate2D coordinate = [(id <MKAnnotation>)[annotations objectAtIndex:i] coordinate];
points[i] = MKMapPointForCoordinate(coordinate);
}
//create MKMapRect from array of MKMapPoint
MKMapRect mapRect = [[MKPolygon polygonWithPoints:points count:count] boundingMapRect];
//convert MKCoordinateRegion from MKMapRect
MKCoordinateRegion region = MKCoordinateRegionForMapRect(mapRect);
//add padding so pins aren't scrunched on the edges
region.span.latitudeDelta *= ANNOTATION_REGION_PAD_FACTOR;
region.span.longitudeDelta *= ANNOTATION_REGION_PAD_FACTOR;
//but padding can't be bigger than the world
if( region.span.latitudeDelta > MAX_DEGREES_ARC ) {
region.span.latitudeDelta = MAX_DEGREES_ARC;
}
if( region.span.longitudeDelta > MAX_DEGREES_ARC ){
region.span.longitudeDelta = MAX_DEGREES_ARC;
}
//and don't zoom in stupid-close on small samples
if( region.span.latitudeDelta < MINIMUM_ZOOM_ARC ) { region.span.latitudeDelta = MINIMUM_ZOOM_ARC; }
if( region.span.longitudeDelta < MINIMUM_ZOOM_ARC ) { region.span.longitudeDelta = MINIMUM_ZOOM_ARC; }
//and if there is a sample of 1 we want the max zoom-in instead of max zoom-out
if( count == 1 )
{
region.span.latitudeDelta = MINIMUM_ZOOM_ARC;
region.span.longitudeDelta = MINIMUM_ZOOM_ARC;
}
[mapView setRegion:region animated:animated];
[mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(120, 120, 150, 80) animated:YES];
// [MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
When I applied it, It is showing this result.
This should work
-(void)zoomToDisplayPolyline:(MKMapView*)mapView polyline:(MKPolyline*)polyline animated:(BOOL)animated
{
[mapView setVisibleMapRect:[polyline boundingMapRect] edgePadding:UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0) animated:animated];
}

Update region to be around Annotations

I have a mapView with some annotations and I'd like to center the map around the annotations. I have the following code:
- (void)updateRegion {
self.needUpdateRegion = NO;
CGRect boundingRect;
BOOL started = NO;
for (id <MKAnnotation> annotation in self.mapView.annotations){
CGRect annotationRect = CGRectMake(annotation.coordinate.longitude, annotation.coordinate.latitude, 0, 0);
if (!started) {
started = YES;
boundingRect = annotationRect;
} else {
boundingRect = CGRectUnion(boundingRect, annotationRect);
}
} if (started) {
boundingRect = CGRectInset(boundingRect, -0.2, -0.2);
if ((boundingRect.size.width >20) && (boundingRect.size.height >20)) {
MKCoordinateRegion region;
region.center.latitude = boundingRect.origin.x + boundingRect.size.width /2;
region.center.longitude = boundingRect.origin.y + boundingRect.size.height / 2;
region.span.latitudeDelta = boundingRect.size.width;
region.span.longitudeDelta = boundingRect.size.height;
[self.mapView setRegion:region animated:YES];
}
}
}
it gets executed in viewDidAppear to make the "sliding effect":
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.needUpdateRegion) [self updateRegion];
}
When I run the app it doesn't do anything and just shows the US.
The annotations are shown (in Europe).
Assuming that updateRegion gets called in the first place (make sure needUpdateRegion is initialized to YES), there are two main problems with the updateRegion method:
It only calls setRegion if the resulting bounding map rect's width and height are 20. Since you are doing the calculations using latitude and longitude degrees, this means setRegion will only get called if the resulting bounding map rect is more than 20 degrees latitude/longitude wide/high. It's not clear if this is what you intended.
The region properties are being set backwards. In the calculation of the bounding map rect, the x values are set to the longitude and the y values are set to the latitude. But when setting region.center.latitude, it is using boundingRect.origin.x instead of boundingRect.origin.y. This applies to the other properties as well so the code there should be:
region.center.longitude = boundingRect.origin.x + boundingRect.size.width /2;
region.center.latitude = boundingRect.origin.y + boundingRect.size.height / 2;
region.span.longitudeDelta = boundingRect.size.width;
region.span.latitudeDelta = boundingRect.size.height;
Note that iOS 7 provides a new convenient method showAnnotations:animated: to automatically show the annotations so you don't have to calculate the region yourself.
So in updateRegion you could do the following:
- (void)updateRegion {
self.needUpdateRegion = NO;
//if showAnnotations:animated: is available, use it...
if ([mapView respondsToSelector:#selector(showAnnotations:animated:)])
{
[self.mapView showAnnotations:mapView.annotations animated:YES];
return;
}
//calculate region manually...
}

MKMap zoom to include annotations and region

I am trying to set the zoom level of the map to include just the annotations, but also don't zoom below say a 3 mile radius, but I also don't the annotations touching the side of the screens. Further, I would also like the annotations once zoomed to be within the bottom half self.view (my map takes up the entire screen).
I tried the following two ways and neither works.
// #1 ///// Always zooms to show my annotation in Texas, zoomed out to far I can't even see US states
-(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 (id <MKAnnotation> annotation in self.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 space on both sides
region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;
//Add a little extra space on bith sides
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1;
region = [aMapView regionThatFits:region];
[self.mapView setRegion:region animated:YES];
}
// #2 ///// This works best, but it zooms in to much; right on top of a house, if there is only one annotation, but I want to see the one annotation and a 4 mile radius. So I never want to zoom less than having 4 square miles shown, UNLESS the user manually zooms it.
MKMapRect zoomRect = MKMapRectNull;
for (id <MKAnnotation> annotation in self.mapView.annotations)
{
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
[self.mapView setVisibleMapRect:zoomRect edgePadding:UIEdgeInsetsMake(300, 300, 100, 300) animated:YES];
MKMapRect zoomRect = MKMapRectNull;
for (id <MKAnnotation> annotation in self.mapView.annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
if (MKMapRectIsNull(zoomRect)) {
zoomRect = pointRect;
}else{
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
}
if (zoomRect.size.width == 0.10) /* for single annotation available in map */
{
zoomRect = MKMapRectMake(zoomRect.origin.x, zoomRect.origin.y, 100000, 100000);
}
[[self mapView] setVisibleMapRect:zoomRect edgePadding:UIEdgeInsetsMake(50, 50, 50, 50) animated:YES];

MKCircle is not updating radius but it's translating

I've to draw an MKCicle into an MKMapView. Then I've to re-draw it when user, through a slider, change the radius. I remove it and I re-create it, re-adding it to the map.
But instead of do what I'm expecting, I see the MKCircle translating over the map, maintaining the same size.
Here's my code:
- (MKOverlayView *)mapView:(MKMapView *)map viewForOverlay:(id)overlay
{
MKOverlayView* overlayView = nil;
if(overlay == self.circle)
{
//if we have not yet created an overlay view for this overlay, create it now.
if(nil == self.circleView)
{
self.circleView = [[[MKCircleView alloc] initWithCircle:self.circle] autorelease];
self.circleView.fillColor = [UIColor blueColor];
self.circleView.strokeColor = [UIColor blueColor];
self.circleView.alpha = 50;
self.circleView.lineWidth = 2;
}
overlayView = self.circleView;
}
return overlayView;
}
-(void)drawPolygonWithLocation
{
[self.mapView removeOverlay: self.circle];
MKCoordinateRegion region;
region.center.latitude = self.geofenceLocation.latitude;
region.center.longitude = self.geofenceLocation.longitude;
region.span.latitudeDelta = 0.005;
region.span.longitudeDelta = 0.005;
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits: region];
[self.mapView setRegion:adjustedRegion animated:TRUE];
self.radius = (double)(slRadius.value);
NSLog(#"Raggio: %f", self.radius);
NSLog(#"Lat: %f, Lon: %f", region.center.latitude, region.center.longitude);
self.circle = [MKCircle circleWithCenterCoordinate:self.geofenceLocation.coordinate radius: self.radius];
NSLog(#"CIRCLE: radius %f Lat: %f, Lon: %f", self.circle.radius, self.circle.coordinate.latitude, self.circle.coordinate.longitude);
[self.mapView addOverlay:self.circle];
}
-(IBAction)updateRadius:(id)sender
{
[self drawPolygonWithLocation];
}
The NSLog is writing into the console right values, the center doesn't change and the radius changes according to the user input.
But, again, the MKCircle translates going on the north-west.
Thanks in advance,
Samuel Rabini
Fixed.
I just add
self.circleView = nil;
before the
[self.mapView addOverlay:self.circle];
in this way it works fine.
Samuel

What's the best way to zoom out and fit all annotations in MapKit

Quick background of what I have going on. UIMapView loads and shows a single annotation (Never more than one). On the menu bar, there is a Locate Me button, on tap the userLocation is found and displayed as a second annotation. I then I have MapView zoom out to fit both those annotations in range but I am unable to find a suitable way of doing so. Depending on where the first annotation is placed in relation to the user location, sometimes it doesn't zoom out enough.
For example, if the annotation is North West of the User, it zooms out fine. But if the annotation is South East of the User, it only zooms out enough that they are just getting cut off and you have to manually zoom out a bit more. Here's the code I am using, variation of some others I have found on SO.
CLLocation *whereIAm = mapView.userLocation.location;
float lat = whereIAm.coordinate.latitude;
float lon = whereIAm.coordinate.longitude;
CLLocationCoordinate2D southWest = {[currentLatitude floatValue], [currentLongitude floatValue]};
CLLocationCoordinate2D northEast = southWest;
southWest.latitude = MIN(southWest.latitude, lat);
southWest.longitude = MIN(southWest.longitude, lon);
northEast.latitude = MAX(northEast.latitude, lat);
northEast.longitude = MAX(northEast.longitude, lon);
CLLocation *locSouthWest = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
CLLocation *locNorthEast = [[CLLocation alloc] initWithLatitude:northEast.latitude longitude:northEast.longitude];
CLLocationDistance meters = [locSouthWest distanceFromLocation: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 = 7.0;
MKCoordinateRegion savedRegion = [mapView regionThatFits:region];
[mapView setRegion:savedRegion animated:YES];
[locSouthWest release];
[locNorthEast release];
Is there a better way built into MapKit to just zoom out so that both annotations have, lets say half an inch between them at the outer frame? I don't really care if the user has to zoom in after, I just want it to zoom out properly.
-(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(MapAnnotation* 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 = [mapView regionThatFits:region];
[mapView setRegion:region animated:YES];
}
Taken from: http://codisllc.com/blog/zoom-mkmapview-to-fit-annotations/
(Use it all the time.)
In iOS7 there's a method that does just that. Just call:
[yourMapView showAnnotations:yourAnnotationsArray animated:YES];
Those who do the monotouch c# coding
BasicMapAnnotation is inherit class from MKAnnotation
private void GetRegion(MKMapView mapView)
{
var userWasVisible = mapView.ShowsUserLocation;
mapView.ShowsUserLocation = false; // ignoring the blue blip
// start with the widest possible viewport
var tl = new CLLocationCoordinate2D(-90, 180); // top left
var br = new CLLocationCoordinate2D(90, -180); // bottom right
foreach (var an in mapView.Annotations)
{
// narrow the viewport bit-by-bit
CLLocationCoordinate2D coordinate = ((BasicMapAnnotation) an).Coordinate;
tl.Longitude = Math.Min(tl.Longitude, coordinate.Longitude);
tl.Latitude = Math.Max(tl.Latitude, coordinate.Latitude);
br.Longitude = Math.Max(br.Longitude, coordinate.Longitude);
br.Latitude = Math.Min(br.Latitude, coordinate.Latitude);
}
var center = new CLLocationCoordinate2D
{
// divide the range by two to get the center
Latitude = tl.Latitude - (tl.Latitude - br.Latitude)*0.5,Longitude = tl.Longitude + (br.Longitude - tl.Longitude)*0.5
};
var span = new MKCoordinateSpan
{
// calculate the span, with 20% margin so pins aren’t on the edge
LatitudeDelta = Math.Abs(tl.Latitude - br.Latitude)*1.2,LongitudeDelta = Math.Abs(br.Longitude - tl.Longitude)*1.2
};
var region = new MKCoordinateRegion {Center = center, Span = span};
region = mapView.RegionThatFits(region); // adjusts zoom level too
mapView.SetRegion(region, true); // animated transition
mapView.ShowsUserLocation =
userWasVisible;
}
You can use this code to show all annotations
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];
if you want to include user location just replace two lines below with the first line of above code
MKMapPoint annotationPoint = MKMapPointForCoordinate(mapView.userLocation.coordinate);
MKMapRect zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);

Resources