Skip to content

Instantly share code, notes, and snippets.

@andrewgleave
Last active November 4, 2022 15:21
Show Gist options
  • Star 83 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save andrewgleave/915374 to your computer and use it in GitHub Desktop.
Save andrewgleave/915374 to your computer and use it in GitHub Desktop.
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];
@SjoerdPerfors
Copy link

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
Copy link

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
Copy link

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

@gongpengjun
Copy link

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
Copy link

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
Copy link

Thank you!!

@stephenomenon
Copy link

This worked perfectly for me.
Thanks a bunch.

@AdrianFerreyra
Copy link

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

@paingpyi
Copy link

It works. Thanks

@malhal
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
Copy link

Awesome !!!

@SudhaGithub
Copy link

Awsome yr :)

@Souf-R
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
Copy link

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

@kussberg
Copy link

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
Copy link

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

@mirjalolbahodirov
Copy link

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
Copy link

ducito commented Nov 8, 2016

Thanks

@VinodJagtap
Copy link

perfect solution!

@rishabh0206
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
Copy link

Nice solution! Thanks

@parveen17
Copy link

nice solutions

@DeepakCarpenter
Copy link

Thanks for sharing ,Saved my time :)
👍

@Skyb0rg
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
Copy link

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
Copy link

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
Copy link

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