Set the Zoom Level of an MKMapView

If you have ever built a web application using the Google Maps API, you are likely intimately familiar with this line of code:

map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13);

The setCenter JavaScript method takes in the center coordinate and a zoom level. The zoom level, as you might expect, determines how far the map should zoom in. The zoom level ranges from 0 (all the way zoomed out) to some upper value (all the way zoomed in). The max zoom level for a particular area depends on the location (for instance, you can’t zoom in too far on North Korea) and the map type (default, satellite, hybrid, terrain, etc). Typically, the max zoom level for an area is somewhere between 15 – 21.

Unfortunately, MapKit on the iPhone does not include a way to set the zoom level. Instead, the zoom level is set implicitly by defining the MKCoordinateRegion of the map’s viewport. When initializing the region, you specify the amount of distance the map displays in the horizontal and vertical directions. The zoom level is set implicitly based on these distance values.

Instead of dealing with this region business, I wrote a category that adds support for setting the zoom level of an MKMapView explicitly. In this post, I’ll give you code you can drop into your own projects and start using immediately. My next post will detail exactly how it works.

The Code

Instead of force-feeding you everything I learned while working on this mini-project, I’ll give you the goods up front. The code below defines a category on MKMapView that gives you the ability to set the zoom level for your map:

// MKMapView+ZoomLevel.h

#import <MapKit/MapKit.h>

@interface MKMapView (ZoomLevel)

- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate


// MKMapView+ZoomLevel.m

#import "MKMapView+ZoomLevel.h"

#define MERCATOR_OFFSET 268435456
#define MERCATOR_RADIUS 85445659.44705395

@implementation MKMapView (ZoomLevel)

#pragma mark -
#pragma mark Map conversion methods

- (double)longitudeToPixelSpaceX:(double)longitude
    return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0);

- (double)latitudeToPixelSpaceY:(double)latitude
    return round(MERCATOR_OFFSET - MERCATOR_RADIUS * logf((1 + sinf(latitude * M_PI / 180.0)) / (1 - sinf(latitude * M_PI / 180.0))) / 2.0);

- (double)pixelSpaceXToLongitude:(double)pixelX
    return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / M_PI;

- (double)pixelSpaceYToLatitude:(double)pixelY
    return (M_PI / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / M_PI;

#pragma mark -
#pragma mark Helper methods

- (MKCoordinateSpan)coordinateSpanWithMapView:(MKMapView *)mapView
    // convert center coordiate to pixel space
    double centerPixelX = [self longitudeToPixelSpaceX:centerCoordinate.longitude];
    double centerPixelY = [self latitudeToPixelSpaceY:centerCoordinate.latitude];
    // determine the scale value from the zoom level
    NSInteger zoomExponent = 20 - zoomLevel;
    double zoomScale = pow(2, zoomExponent);
    // scale the map’s size in pixel space
    CGSize mapSizeInPixels = mapView.bounds.size;
    double scaledMapWidth = mapSizeInPixels.width * zoomScale;
    double scaledMapHeight = mapSizeInPixels.height * zoomScale;
    // figure out the position of the top-left pixel
    double topLeftPixelX = centerPixelX - (scaledMapWidth / 2);
    double topLeftPixelY = centerPixelY - (scaledMapHeight / 2);
    // find delta between left and right longitudes
    CLLocationDegrees minLng = [self pixelSpaceXToLongitude:topLeftPixelX];
    CLLocationDegrees maxLng = [self pixelSpaceXToLongitude:topLeftPixelX + scaledMapWidth];
    CLLocationDegrees longitudeDelta = maxLng - minLng;
    // find delta between top and bottom latitudes
    CLLocationDegrees minLat = [self pixelSpaceYToLatitude:topLeftPixelY];
    CLLocationDegrees maxLat = [self pixelSpaceYToLatitude:topLeftPixelY + scaledMapHeight];
    CLLocationDegrees latitudeDelta = -1 * (maxLat - minLat);
    // create and return the lat/lng span
    MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
    return span;

#pragma mark -
#pragma mark Public methods

- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
    // clamp large numbers to 28
    zoomLevel = MIN(zoomLevel, 28);
    // use the zoom level to compute the region
    MKCoordinateSpan span = [self coordinateSpanWithMapView:self centerCoordinate:centerCoordinate andZoomLevel:zoomLevel];
    MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
    // set the region like normal
    [self setRegion:region animated:animated];


If you’re wondering why this works, check out my next post where I describe in gruesome detail the math behind the code.

On the other hand, if you don’t really care how it works, just that it does work, copy and paste away, my friend.

Test the Code

To test the category, assuming you have a view controller with an MKMapView instance, you can use the following code:

// ZoomLevelTestViewController.m

#import "MKMapView+ZoomLevel.h"

#define GEORGIA_TECH_LATITUDE 33.777328
#define GEORGIA_TECH_LONGITUDE -84.397348

#define ZOOM_LEVEL 14

- (void)viewDidAppear:(BOOL)animated
    [super viewDidAppear:animated];
    CLLocationCoordinate2D centerCoord = { GEORGIA_TECH_LATITUDE, GEORGIA_TECH_LONGITUDE };
    [mapView setCenterCoordinate:centerCoord zoomLevel:ZOOM_LEVEL animated:NO];

And, viola! Your map should zoom in to where you can see the Georgia Tech campus.


To verify that the zoom level is set correctly, I wrote a simple web-based Google Maps application to make sure the web and native zoom levels matched. Both apps were centered at {33.777328, -84.397348} (Georgia Tech). In the images below, the iPhone on the left is running the native app and the iPhone on the right is running the web app:

Zoom Level 2: Native app on the left. Web app on the right.

Zoom Level 10: Native app on the left. Web app on the right.

Zoom Level 18: Native app on the left. Web app on the right.

As you can see, they match.

That’s a Wrap

By using the MKMapView+ZoomLevel category, you won’t have to bother setting the region at all. If you are like me and have no intuition for how to set the map’s region, then hopefully this code will give you a bit more control in setting your map’s zoom level.

Next time, I’ll go over exactly why the code above works. But, for now, enjoy the freedom to set zoom levels!

80 Responses to “Set the Zoom Level of an MKMapView”

  1. Great job

    But from this how do I will get current zoom level of map view???

  2. Thanks; this saved me several minutes of my life. Like… at least 4 minutes. It looks like you posted this a few years ago, but wherever you are, I hope you’re wildly successful.

    // John

  3. Thanks a lot for this post my friend,

    I just started developing for iOS and I was surprised why there was no way set a zoomlevel. When a user zooms, it’s because the user wants to know the exact spot of the location and not the area.

    By the way, you’re post was very easy to follow (you should be a teacher some day).

  4. I too want to get the zoom level of a map.

  5. Thanks for the great tutorial. But When i used this class with 100′s of map points in same lat longs, the map points are over lapping each other and unable to click particular map point Please check this link i want to display using spiderfy leafleft animation.. SO by using spiderfy animation we can achive this . any idea?

  6. Your post was very helpful that can help me convert a zoom from Google map to a span on Apple map. Thanks a lot.

    Do you know how to get a zoom level if I have a span. I think I need to do the contrary but I not very good at math and those calculation are really complicated

    Thanks in advance

  7. If anyone needs it I translated this to a Swit extension for use in a project I’m working on.
    I’ll be adding the inverse operation soon (need it for same project). Just working out some kinks in it now.

    Thanks Troy for this and thanks to Dave in the comments for the inverse function.

  8. For those who need to get zoom level, use that code :
    @interface MKMapView (ZoomLevelGetter)
    - (int)getZoomLevel;

    @implementation MKMapView (ZoomLevelGetter)

    - (int)getZoomLevel

    CLLocationDegrees longitudeDelta = self.region.span.longitudeDelta;
    CGFloat mapWidthInPixels = self.bounds.size.width*2;//2 is for retina display
    double zoomScale = longitudeDelta * MERCATOR_RADIUS * M_PI / (180.0 * mapWidthInPixels);
    double zoomer = MAX_GOOGLE_LEVELS – log2( zoomScale );
    if ( zoomer < 0 ) zoomer = 0;
    zoomer = round(zoomer);
    return (int)zoomer;


  9. Thanks to Mike for the Swift Extension! I do need the inverse function, so I hope you get around to posting it.

  10. プロムドレスのアクセサリーの多くには、これまでに中立的なヒョウ?プリントを混合することを見つけるのは喜ぶかもしれません。それを変形できるので決してかつらを折ってください。金属閉鎖なしでポニーテールホルダーを使用してここでは、あなたは男性の髪のためのいくつかのヘアケアのヒントを与えます。暗い色が全体的な体はあなたの顔のモデルにするのを助けるかもしれない内に支払う思考の素晴らしい方法です。この製品の高度な技術とその顕著な特徴の一つは、小さな板を使用しています。父のカレン?メドレー株に、マイケルコースのハンドバッグ

  11. Hammertoes have long plagued the human foot, causing pain and discomfort in shoes.
    If the injury is not severe, the recovery should
    be fast. If you don’t know the cause of a pain, tap on the image of that pain and you’ll probably
    get a measure of relief.

  12. I really like looking through a post that can make people think.
    Also, thank you for permitting me to comment!

  13. This is a very good tip particularly to those new to the blogosphere.
    Brief but very precise info… Thanks for sharing this one.

    A must read post!

  14. The idea to put a truck in Toms River, he said, came about as a result of requests from residents.

  15. And in 1965, Air Force Maj. Jack Bond, the deputy for reconnaissance at the Directorate of Advanced Recon Planning, reported seeing an unidentified object moving in a sine wave pattern while on a flight out of Wright Patterson.

  16. ”You just know if we do our job in the middle, we have the strikepower out wide to get the job done.”

  17. Brilliant !!!. It Was true I Think..

  18. Hi there! Thanks for this post! I have ported the code to Swift 2.3

  19. Interestingly, after studying the database of 1600 cities that this list is formed from, the most polluted city in America is Fresno, California by a huge It is more polluted that Mexico City, more than many cities in China or India, and more than every European city west of Even subtracting the pollution that comes from Asia across the pacific it is more polluted than Mexico

  20. The racist DONALD TRUMP said to NYT “I believe HITLER was RIGHT”. zfr Donald Trump is a racist with SEWER and the DailyStormer, he listens to satanic 666 sexist music… just google “Donald Trump SEWER 2154″ and see FOR YOURSELF!! THE MUSIC cb VIDEO IS about the KKK and Adfolf Hitler raping a 12 year old African-American WOMAN OF COLOR in front of her parents and then hanging MLK with Emma Watson and Taylor Swift!! TAYLOR SWIFT the racist white privileged cvnt said she voted “for donald trump twice” in her OWN WORDS!!! Say no to hate, say no to SEWER, say no to l DONALD TRUMP and EMMA WATSON and Tatylor Swift !! Deport racism today ldy.

  21. Great article I think. Thanks for sharing with us ! I really inspiring too.

  22. I Created a Swift Version:

  23. Awesome, this is what I was browsing for in google

  24. The prinary yyou must consider will bbe your budget. Platinum iss extremely popular
    option for rings but it is quite expensive metal.
    White gold is easily the most popular one as well as the yellow gold.

  25. Undeniably consider that which you said. Your favourite reason appeared to be on the internet the simplest factor to have in mind of. I say to you, I certainly get irked whilst people think about concerns that they plainly don’t realize about. You controlled to hit the nail upon the highest as smartly as|and also|and} outlined out the entire thing without having side effect , people could take a signal. Will probably be again to get more. Thanks
    Filling Machines

  26. Wow its great !

  27. Đồng phục quần áo, tin tức nóng hổi

  28. I coulkd not resist commenting. Perfectly written!

Leave a Reply