Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Zooms out a MKMapView to enclose all its annotations (inc. current location)
MKMapRect zoomRect = MKMapRectNull;
for (id <MKAnnotation> annotation in mapView.annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);
if (MKMapRectIsNull(zoomRect)) {
zoomRect = pointRect;
} else {
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
}
[mapView setVisibleMapRect:zoomRect animated:YES];
@agasbzj

This comment has been minimized.

Copy link

agasbzj commented Jan 31, 2013

it works~thanks

@danielmartinprieto

This comment has been minimized.

Copy link

danielmartinprieto commented Feb 13, 2013

Hi. When you have only two annotations (say one annotation plus the user location) the map shows both of them, but right on the edges of the screen, and it doesn't look very nice. Do you know how could I add some extra space between annotations and the edges? Thanks.

@danielmartinprieto

This comment has been minimized.

Copy link

danielmartinprieto commented Feb 16, 2013

Just changing the last sentence

[mapView setVisibleMapRect:zoomRect animated:YES];

for

[mapView setVisibleMapRect:zoomRect edgePadding:UIEdgeInsetsMake(10, 10, 10, 10) animated:YES];

you'll get some extra space between annotations and the edges.

@SjoerdPerfors

This comment has been minimized.

Copy link

SjoerdPerfors commented Jun 28, 2013

Thanks daniel-reckoder ! Works perfect.

One note: it will choose automatically a zoom level. This means you can't define the edge exactly, it's more like a minimum value.

@coryhymel

This comment has been minimized.

Copy link

coryhymel commented Jan 23, 2014

I would change this:

MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);

to this:

MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);

Just so it doesn't accidentally end up infinitesimally small

@Isuru-Nanayakkara

This comment has been minimized.

Copy link

Isuru-Nanayakkara commented Jan 25, 2014

What values should I change to define my own zoom level?

@gongpengjun

This comment has been minimized.

Copy link

gongpengjun commented Apr 29, 2014

It would be better to change this:

[self setVisibleMapRect:zoomRect edgePadding:edgePadding animated:animated];

to

zoomRect = [self mapRectThatFits:zoomRect];
if(!MKMapRectIsEmpty(zoomRect)) {
    [self setVisibleMapRect:zoomRect edgePadding:edgePadding animated:animated];
}
@kerbelda

This comment has been minimized.

Copy link

kerbelda commented May 21, 2014

Not working for me. Zooms in to the annotation, but isn't far out enough to include my location. Only change I made was mapView.annotations to self.mapView.annotations. Copy pasted everything into viewDidLoad.

@lonelytango

This comment has been minimized.

Copy link

lonelytango commented Jun 18, 2014

Thank you!!

@ten-ten-steve

This comment has been minimized.

Copy link

ten-ten-steve commented Jun 23, 2014

This worked perfectly for me.
Thanks a bunch.

@AdrianFerreyra

This comment has been minimized.

Copy link

AdrianFerreyra commented Jul 17, 2014

Thank you very much for the idea (Y) You saved me some precious time

@philokezzar

This comment has been minimized.

Copy link

philokezzar commented Apr 15, 2015

It works. Thanks

@malhal

This comment has been minimized.

Copy link

malhal commented Apr 23, 2015

@philokezzar, as of iOS 7 you can simply use:

 // 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);
@rajubd49

This comment has been minimized.

Copy link

rajubd49 commented May 10, 2015

Awesome !!!

@SudhaGithub

This comment has been minimized.

Copy link

SudhaGithub commented Jun 27, 2015

Awsome yr :)

@Souf-R

This comment has been minimized.

Copy link

Souf-R commented Sep 2, 2015

Here is a swift version:

func fitMapViewToAnnotaionList(annotations: [MKPointAnnotation]) -> Void {
    let mapEdgePadding = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
    var zoomRect:MKMapRect = MKMapRectNull

    for index in 0..<annotations.count {
        let annotation = annotations[index]
        var aPoint:MKMapPoint = MKMapPointForCoordinate(annotation.coordinate)
        var rect:MKMapRect = MKMapRectMake(aPoint.x, aPoint.y, 0.1, 0.1)

        if MKMapRectIsNull(zoomRect) {
            zoomRect = rect
        } else {
            zoomRect = MKMapRectUnion(zoomRect, rect)
        }
    }

    mapView.setVisibleMapRect(zoomRect, edgePadding: mapEdgePadding, animated: true)
}
@oNguyenDa

This comment has been minimized.

Copy link

oNguyenDa commented Oct 16, 2015

Could you setVisibleMapRect but don't focus center on sceen?

@kussberg

This comment has been minimized.

Copy link

kussberg commented Nov 18, 2015

Hi guys, i have done following in my project not to have a function but created an extension for MapView and it works like charm! Thanks for the function Souf-R

//
//  KMMapViewExtension.swift
//

import Foundation
import MapKit

extension MKMapView {
    func fitMapViewToAnnotaionList() -> Void {
        let mapEdgePadding = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
        var zoomRect:MKMapRect = MKMapRectNull

        for index in 0..<self.annotations.count {
            let annotation = self.annotations[index]
            let aPoint:MKMapPoint = MKMapPointForCoordinate(annotation.coordinate)
            let rect:MKMapRect = MKMapRectMake(aPoint.x, aPoint.y, 0.1, 0.1)

            if MKMapRectIsNull(zoomRect) {
                zoomRect = rect
            } else {
                zoomRect = MKMapRectUnion(zoomRect, rect)
            }
        }
        self.setVisibleMapRect(zoomRect, edgePadding: mapEdgePadding, animated: true)
    }
}
@matarali

This comment has been minimized.

Copy link

matarali commented Dec 14, 2015

What if you had multiple annotations but you only ever wanted to zoom out to the closest one to your current location?

@mirjalolbahodirov

This comment has been minimized.

Copy link

mirjalolbahodirov commented Jul 27, 2016

All gists which are showed above, cannot include current location.
I just added some changes to @Souf-R's function
This version can include device's current location:

func fitMapViewToAnnotaionList(annotations: [MKPointAnnotation], userLocation: CLLocationCoordinate2D) -> Void {
        let mapEdgePadding = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
        var zoomRect:MKMapRect = MKMapRectNull

        for index in 0..<annotations.count {
            let annotation = annotations[index]
            let aPoint:MKMapPoint = MKMapPointForCoordinate(annotation.coordinate)
            let rect:MKMapRect = MKMapRectMake(aPoint.x, aPoint.y, 0.1, 0.1)

            if MKMapRectIsNull(zoomRect) {
                zoomRect = rect
            } else {
                zoomRect = MKMapRectUnion(zoomRect, rect)
            }
        }

        let aPoint:MKMapPoint = MKMapPointForCoordinate(userLocation)
        let rect:MKMapRect = MKMapRectMake(aPoint.x, aPoint.y, 0.1, 0.1)

        if MKMapRectIsNull(zoomRect) {
            zoomRect = rect
        } else {
            zoomRect = MKMapRectUnion(zoomRect, rect)
        }
        mapView.setVisibleMapRect(zoomRect, edgePadding: mapEdgePadding, animated: true)
    }
@ducito

This comment has been minimized.

Copy link

ducito commented Nov 8, 2016

Thanks

@VinodJagtap

This comment has been minimized.

Copy link

VinodJagtap commented Jan 23, 2017

perfect solution!

@rishabh0206

This comment has been minimized.

Copy link

rishabh0206 commented Mar 6, 2017

Great solution!!! Thanks!! Here is the swift version of the code with padding set -

        var zoomRect = MKMapRectNull
        for annotation in mkMapView.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)
            }
        }
        mkMapView.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(40, 40, 40, 40), animated: true)
@user-viprak

This comment has been minimized.

Copy link

user-viprak commented May 3, 2017

Nice solution! Thanks

@parveen17

This comment has been minimized.

Copy link

parveen17 commented Dec 7, 2017

nice solutions

@DeepakCarpenter

This comment has been minimized.

Copy link

DeepakCarpenter commented Dec 14, 2017

Thanks for sharing ,Saved my time :)
👍

@Skyb0rg

This comment has been minimized.

Copy link

Skyb0rg commented Dec 22, 2017

Thanks for the Solution, works perfekt.

I needed to run

mkMapView.layoutIfNeeded()

before
mkMapView.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(40, 40, 40, 40), animated: true)

if i did not run layoutIfNeeded() the EdgeInset is not set in ViewDidLoad or ViewDidApear
or the zoom is a bit to large.

@Blackjacx

This comment has been minimized.

Copy link

Blackjacx commented Feb 1, 2018

Even easier:

static func fittingMapRect(forCoordinates coordinates: [CLLocationCoordinate2D]) -> MKMapRect {

    guard !coordinates.isEmpty else {
        return MKMapRectNull
    }
    let mapPoints = coordinates.map { MKMapPointForCoordinate($0) }
    let mapRects = mapPoints.map { MKMapRect(origin: $0, size: MKMapSize(width: 1, height: 1)) }
    let fittingRect = mapRects.reduce(MKMapRectNull, MKMapRectUnion)
    return fittingRect
}
@basememara

This comment has been minimized.

Copy link

basememara commented Feb 23, 2018

Unfortunately, showAnnotations is too zoomed out and doesn't look good. Thanks for this code everyone, here's my condensed version:

mapView.setVisibleMapRect(
    mapView.annotations.reduce(MKMapRectNull) { result, next in
        let point = MKMapPointForCoordinate(next.coordinate)
        let rect = MKMapRectMake(point.x, point.y, 0, 0)
        guard !MKMapRectIsNull(result) else { return rect }
        return MKMapRectUnion(result, rect)
    },
    edgePadding: UIEdgeInsetsMake(20, 20, 40, 20),
    animated: true
)
@hemangshah

This comment has been minimized.

Copy link

hemangshah commented Apr 4, 2018

 extension MKMapView {

    var MERCATOR_OFFSET : Double {
        return 268435456.0
    }

    var MERCATOR_RADIUS : Double  {
        return 85445659.44705395
    }

    private func longitudeToPixelSpaceX(longitude: Double) -> Double {
        return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * Double.pi / 180.0)
    }

    private func latitudeToPixelSpaceY(latitude: Double) -> Double {
        return round(MERCATOR_OFFSET - MERCATOR_RADIUS * log((1 + sin(latitude * Double.pi / 180.0)) / (1 - sin(latitude * Double.pi / 180.0))) / 2.0)
    }

    private  func pixelSpaceXToLongitude(pixelX: Double) -> Double {
        return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / Double.pi;
    }

    private func pixelSpaceYToLatitude(pixelY: Double) -> Double {
        return (Double.pi / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / Double.pi;
    }

    private func coordinateSpan(withMapView mapView: MKMapView, centerCoordinate: CLLocationCoordinate2D, zoomLevel: UInt) ->MKCoordinateSpan {
        let centerPixelX = longitudeToPixelSpaceX(longitude: centerCoordinate.longitude)
        let centerPixelY = latitudeToPixelSpaceY(latitude: centerCoordinate.latitude)

        let zoomExponent = Double(20 - zoomLevel)
        let zoomScale = pow(2.0, zoomExponent)

        let mapSizeInPixels = mapView.bounds.size
        let scaledMapWidth =  Double(mapSizeInPixels.width) * zoomScale
        let scaledMapHeight = Double(mapSizeInPixels.height) * zoomScale

        let topLeftPixelX = centerPixelX - (scaledMapWidth / 2);
        let topLeftPixelY = centerPixelY - (scaledMapHeight / 2);

        // find delta between left and right longitudes
        let minLng = pixelSpaceXToLongitude(pixelX: topLeftPixelX)
        let maxLng = pixelSpaceXToLongitude(pixelX: topLeftPixelX + scaledMapWidth)
        let longitudeDelta = maxLng - minLng;

        let minLat = pixelSpaceYToLatitude(pixelY: topLeftPixelY)
        let maxLat = pixelSpaceYToLatitude(pixelY: topLeftPixelY + scaledMapHeight)
        let latitudeDelta = -1 * (maxLat - minLat);

        let span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta)
        return span
    }

    func zoom(toCenterCoordinate centerCoordinate:CLLocationCoordinate2D ,zoomLevel: UInt) {
        let zoomLevel = min(zoomLevel, 20)
        let span = self.coordinateSpan(withMapView: self, centerCoordinate: centerCoordinate, zoomLevel: zoomLevel)
        let region = MKCoordinateRegionMake(centerCoordinate, span)
        self.setRegion(region, animated: true)

    }
}

Original Code Credit: http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/
Swift 2.3 Translation Credit: https://stackoverflow.com/users/2729171/apinho
Swift 4.x Swift Translation: By me. 👍

@mxcl

This comment has been minimized.

Copy link

mxcl commented Apr 9, 2018

This is the easiest way:

map.layoutMargins = UIEdgeInsets(top: 10, right: 10, bottom: 10, left: 10)
map.showAnnotations(map.annotations, animated: true)

It works! I promise.

@SimonJanevski

This comment has been minimized.

Copy link

SimonJanevski commented Mar 6, 2019

Thanks for this!
In my particular case, I had annotations and overlays for them, like the user's location blue circle overlay (userLocation.horizontalAccuracy). To fit all the annotations and their overlays, I modified this a bit and got these two extensions.

extension MKMapView {
    func setVisibleMapRectToFitAllAnnotations(animated: Bool = true,
                                              shouldIncludeUserAccuracyRange: 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)
        }

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

This comment has been minimized.

Copy link

anirudhamahale commented Nov 4, 2019

I just modified SimonJanevski's answer to show all annotations and overlays present on MapView.

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)
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.