128

I have a map which shows correctly, the only thing I want to do now is set the zoom level when it loads. Is there a way to do this?

Thanks

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
some_id
  • 29,466
  • 62
  • 182
  • 304

15 Answers15

219

I found myself a solution, which is very simple and does the trick. Use MKCoordinateRegionMakeWithDistance in order to set the distance in meters vertically and horizontally to get the desired zoom. And then of course when you update your location you'll get the right coordinates, or you can specify it directly in the CLLocationCoordinate2D at startup, if that's what you need to do:

CLLocationCoordinate2D noLocation;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(noLocation, 500, 500);
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:viewRegion];          
[self.mapView setRegion:adjustedRegion animated:YES];
self.mapView.showsUserLocation = YES;

Swift:

let location = ...
let region = MKCoordinateRegion( center: location.coordinate, latitudinalMeters: CLLocationDistance(exactly: 5000)!, longitudinalMeters: CLLocationDistance(exactly: 5000)!)
mapView.setRegion(mapView.regionThatFits(region), animated: true)
Sourabh Sharma
  • 8,222
  • 5
  • 68
  • 78
Carnal
  • 21,744
  • 6
  • 60
  • 75
  • 3
    This should be the selected answer. I tried a lot of the other proposed solutions but none of them worked properly. This code is simple and effective. – Levi Roberts May 08 '14 at 07:07
  • 1
    Nice answer. However the zoom will be different depending on the screen size, no? – Vinzius Aug 03 '15 at 13:45
  • 1
    Interestingly, `MKCoordinateRegionMakeWithDistance` is still around in Swift. This solution works! – LinusGeffarth Feb 04 '19 at 10:53
50

Based on the fact that longitude lines are spaced apart equally at any point of the map, there is a very simple implementation to set the centerCoordinate and zoomLevel:

@interface MKMapView (ZoomLevel)

@property (assign, nonatomic) NSUInteger zoomLevel;

- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
                  zoomLevel:(NSUInteger)zoomLevel
                   animated:(BOOL)animated;

@end


@implementation MKMapView (ZoomLevel)

- (void)setZoomLevel:(NSUInteger)zoomLevel {
    [self setCenterCoordinate:self.centerCoordinate zoomLevel:zoomLevel animated:NO];
}

- (NSUInteger)zoomLevel {
    return log2(360 * ((self.frame.size.width/256) / self.region.span.longitudeDelta)) + 1;
}

- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
zoomLevel:(NSUInteger)zoomLevel animated:(BOOL)animated {
    MKCoordinateSpan span = MKCoordinateSpanMake(0, 360/pow(2, zoomLevel)*self.frame.size.width/256);
    [self setRegion:MKCoordinateRegionMake(centerCoordinate, span) animated:animated];
}

@end
Aron Lindberg
  • 343
  • 2
  • 6
quentinadam
  • 3,058
  • 2
  • 27
  • 42
  • Minor corrections: `- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(NSUInteger)zoomLevel animated:(BOOL)animated { MKCoordinateSpan span = MKCoordinateSpanMake(0, 360/pow(2, zoomLevel)*self.frame.size.width/256); [self setRegion:MKCoordinateRegionMake(centerCoordinate, span) animated:animated]; }` – Monobono Feb 28 '13 at 12:26
  • Thanks! Yes you are right, I actually took the code out from my project where it was a function rather than an addition to MKMapView. I have just edited to code to reflect your correction. – quentinadam Feb 28 '13 at 15:36
  • +1 for the unobtrusive way of adding this functionality via a category – leviathan Mar 13 '13 at 11:47
  • 1
    What is the reverse of that formula, to work out the current zoom level? – Nick Dec 14 '13 at 23:11
  • 1
    I think it is this: `double z = log2(360 * ((self.mapView.frame.size.width/256) / self.mapView.region.span.longitudeDelta));` – Nick Dec 14 '13 at 23:30
  • Added updated answer, inspired by Adil's work - Thank you for this!! So much simpler than most other methods. http://stackoverflow.com/a/20589521/224707 – Nick Dec 14 '13 at 23:37
  • Can someone please explain to me what the `256` represents? – devios1 Jun 23 '15 at 22:53
  • 1
    @devios, at zoom level 1, the whole world (360°) fits in 1 tile of 256px wide. At zoom level 2, the whole world (360°) fits in 2 tiles of 256px (512px). At zoom level 3, the whole world (360°) fits in 4 tiles of 256px (1024px), etc. – quentinadam Jun 24 '15 at 09:20
  • You add +1 to get the zoomLevel, so maybe you should remove 1 too when calculating the span, nop ? – Vinzius Aug 03 '15 at 14:02
  • The +1 is only due to the rounding down to an integer when returning the zoomLevel. You can check by setting the zoomLevel and then returning it, it will be the same. – quentinadam Aug 25 '15 at 13:24
  • If I set the zoomLevel 14 then getZoomLevel returns 15. I think that +1 is not needed. – ViruMax Oct 16 '19 at 08:33
30

It's not built in, but I've seen / used this code. This allows you to use this:

[mapView setCenterCoordinate:myCoord zoomLevel:13 animated:YES];

Note: This is not my code, I did not write it, so therefore can't take credit for it

mindriot
  • 5,413
  • 1
  • 25
  • 34
PostMan
  • 6,899
  • 1
  • 43
  • 51
  • 1
    wow, its a lot of code, u would think it should be built in. thanks. will have a look hows its done. – some_id Nov 15 '10 at 22:51
  • 1
    You can get the .m and .h file, add it to your project, then reference it in your map view controller, and use it as if it were a method on MKMapView, oh the joys of categories! – PostMan Nov 15 '10 at 22:55
  • 2
    Didnt work for me, it just displays the same zoom level as before. I must be doing something wrong. – some_id Nov 19 '10 at 13:30
17

You can also zoom by using MKCoordinateRegion and setting its span latitude & longitude delta. Below is a quick reference and here is the iOS reference. It won't do anything fancy but should allow you to set zoom when it draws the map.


MKCoordinateRegion region;
region.center.latitude = {desired lat};
region.center.longitude = {desired lng};
region.span.latitudeDelta = 1;
region.span.longitudeDelta = 1;
mapView.region = region;

Edit 1:

MKCoordinateRegion region;
region.center.latitude = {desired lat};
region.center.longitude = {desired lng};
region.span.latitudeDelta = 1;
region.span.longitudeDelta = 1;
region = [mapView regionThatFits:region];
[mapView setRegion:region animated:TRUE];
Chintan Patel
  • 3,175
  • 3
  • 30
  • 36
DerekH
  • 860
  • 5
  • 11
  • 1
    This made no difference for me, when I change some values, it just doesnt load the map. – some_id Nov 19 '10 at 13:30
  • Are you setting this when the map loads or are you trying to manipulate after load has taken place? Are you using 1 or a smaller number as your deltas? Just trying to understand the requirements. – DerekH Nov 19 '10 at 16:01
  • I set it before runtime. I tested values above and below 1. – some_id Nov 25 '10 at 19:28
  • 1
    Good answer but try to change latitude, longitude delta to 0.1 - it is zoomed more. – Daniel Krzyczkowski Aug 03 '16 at 05:28
14

Swift implementation

import Foundation
import MapKit

class MapViewWithZoom: MKMapView {

    var zoomLevel: Int {
        get {
            return Int(log2(360 * (Double(self.frame.size.width/256) / self.region.span.longitudeDelta)) + 1);
        }

        set (newZoomLevel){
            setCenterCoordinate(coordinate:self.centerCoordinate, zoomLevel: newZoomLevel, animated: false)
        }
    }

    private func setCenterCoordinate(coordinate: CLLocationCoordinate2D, zoomLevel: Int, animated: Bool) {
        let span = MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 360 / pow(2, Double(zoomLevel)) * Double(self.frame.size.width) / 256)
        setRegion(MKCoordinateRegion(center: coordinate, span: span), animated: animated)
    }
}
ViruMax
  • 1,216
  • 3
  • 16
  • 41
Allan Spreys
  • 5,287
  • 5
  • 39
  • 44
  • 1
    I'm not 100% sure, but I guess that when you create your `IBOutlet` to your `map`, you define it as a `MapViewWithZoom` instead of a simple `MKMapView`. Then, you can just set the zoom level with `map.zoomLevel = 1` or `map.zoomLevel = 0.5` – Zonker.in.Geneva Mar 10 '17 at 12:19
  • 1
    some comments on this would be more helpful It is working in Swift 3. – nyxee Jun 07 '17 at 10:16
  • 1
    Great solution! But I like it more as an Extension, and there's one weird thing: in order to actually zoom out, need to decrement by 2 like `mapView.zoomLevel -= 2` – Alexander Apr 05 '20 at 16:44
12

A simple Swift implementation, if you use outlets.

@IBOutlet weak var mapView: MKMapView! {
    didSet {
        let noLocation = CLLocationCoordinate2D()
        let viewRegion = MKCoordinateRegionMakeWithDistance(noLocation, 500, 500)
        self.mapView.setRegion(viewRegion, animated: false)
    }
}

Based on @Carnal's answer.

swennemen
  • 945
  • 1
  • 14
  • 24
7

For Swift 3 it's pretty fast forward:

private func setMapRegion(for location: CLLocationCoordinate2D, animated: Bool)
{
    let viewRegion = MKCoordinateRegionMakeWithDistance(location, <#T##latitudinalMeters: CLLocationDistance##CLLocationDistance#>, <#T##longitudinalMeters: CLLocationDistance##CLLocationDistance#>)
    MapView.setRegion(viewRegion, animated: animated)
}

Just define the lat-, long-Meters <CLLocationDistance> and the mapView will fit the zoom level to your values.

zero3nna
  • 2,770
  • 30
  • 28
  • What do you mean by "the mapView will fit the zoom level to your values" ? I assume the OP wants to set the zoom level himself or how would you do that given the input you suggest? – riper Mar 29 '17 at 20:33
6

Based on @AdilSoomro's great answer. I have come up with this:

@interface MKMapView (ZoomLevel)
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
                  zoomLevel:(NSUInteger)zoomLevel
                   animated:(BOOL)animated;

-(double) getZoomLevel;
@end



@implementation MKMapView (ZoomLevel)

- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
                  zoomLevel:(NSUInteger)zoomLevel animated:(BOOL)animated {
    MKCoordinateSpan span = MKCoordinateSpanMake(0, 360/pow(2, zoomLevel)*self.frame.size.width/256);
    [self setRegion:MKCoordinateRegionMake(centerCoordinate, span) animated:animated];
}


-(double) getZoomLevel {
    return log2(360 * ((self.frame.size.width/256) / self.region.span.longitudeDelta));
}

@end
Community
  • 1
  • 1
Nick
  • 2,803
  • 1
  • 39
  • 59
3

I hope following code fragment would help you.

- (void)handleZoomOutAction:(id)sender {
    MKCoordinateRegion newRegion=MKCoordinateRegionMake(mapView.region.center,MKCoordinateSpanMake(mapView.region.s       pan.latitudeDelta/0.5, mapView.region.span.longitudeDelta/0.5));
    [mapView setRegion:newRegion];
}


- (void)handleZoomInAction:(id)sender {
    MKCoordinateRegion newRegion=MKCoordinateRegionMake(mapView.region.center,MKCoordinateSpanMake(mapView.region.span.latitudeDelta*0.5, mapView.region.span.longitudeDelta*0.5));
    [mapView setRegion:newRegion];
}

You can choose any value in stead of 0.5 to reduce or increase zoom level. I have used these methods on click of two buttons.

Viccari
  • 9,029
  • 4
  • 43
  • 77
dispatchMain
  • 1,617
  • 2
  • 18
  • 30
3

Swift:

Map.setRegion(MKCoordinateRegion(center: locValue, latitudinalMeters: 200, longitudinalMeters: 200), animated: true)

locValue is your coordinate.

time.
  • 87
  • 11
2

A Swift 2.0 answer utilising NSUserDefaults to save and restore the map's zoom and position.

Function to save the map position and zoom:

func saveMapRegion() {
    let mapRegion = [
        "latitude" : mapView.region.center.latitude,
        "longitude" : mapView.region.center.longitude,
        "latitudeDelta" : mapView.region.span.latitudeDelta,
        "longitudeDelta" : mapView.region.span.longitudeDelta
    ]
    NSUserDefaults.standardUserDefaults().setObject(mapRegion, forKey: "mapRegion")
}

Run the function each time the map is moved:

func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) 
{
        saveMapRegion();
}

Function to save the map zoom and position:

func restoreMapRegion() 
{
    if let mapRegion = NSUserDefaults.standardUserDefaults().objectForKey("mapRegion") 
    {

        let longitude = mapRegion["longitude"] as! CLLocationDegrees
        let latitude = mapRegion["latitude"] as! CLLocationDegrees
        let center = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)

        let longitudeDelta = mapRegion["latitudeDelta"] as! CLLocationDegrees
        let latitudeDelta = mapRegion["longitudeDelta"] as! CLLocationDegrees
        let span = MKCoordinateSpan(latitudeDelta: latitudeDelta, longitudeDelta: longitudeDelta)

        let savedRegion = MKCoordinateRegion(center: center, span: span)

        self.mapView.setRegion(savedRegion, animated: false)
    }
}

Add this to viewDidLoad:

restoreMapRegion()
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
David T
  • 2,724
  • 1
  • 17
  • 27
2

MKMapView extension based on this answer (+ floating-point zoom level accuracy):

import Foundation
import MapKit

extension MKMapView {
    var zoomLevel: Double {
        get {
            return log2(360 * (Double(self.frame.size.width / 256) / self.region.span.longitudeDelta)) + 1
        }

        set (newZoomLevel){
            setCenterCoordinate(coordinate:self.centerCoordinate, zoomLevel: newZoomLevel, animated: false)
        }
    }

    private func setCenterCoordinate(coordinate: CLLocationCoordinate2D, zoomLevel: Double, animated: Bool) {
        let span = MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 360 / pow(2, zoomLevel) * Double(self.frame.size.width) / 256)
        setRegion(MKCoordinateRegion(center: coordinate, span: span), animated: animated)
    }
}
Marcin Piela
  • 126
  • 5
1

I know this is a late reply, but I've just wanted to address the issue of setting the zoom level myself. goldmine's answer is great but I found it not working sufficiently well in my application.

On closer inspection goldmine states that "longitude lines are spaced apart equally at any point of the map". This is not true, it is in fact latitude lines that are spaced equally from -90 (south pole) to +90 (north pole). Longitude lines are spaced at their widest at the equator, converging to a point at the poles.

The implementation I have adopted is therefore to use the latitude calculation as follows:

@implementation MKMapView (ZoomLevel)

- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate
    zoomLevel:(NSUInteger)zoom animated:(BOOL)animated
{
    MKCoordinateSpan span = MKCoordinateSpanMake(180 / pow(2, zoom) * 
        self.frame.size.height / 256, 0);
    [self setRegion:MKCoordinateRegionMake(coordinate, span) animated:animated];
}

@end

Hope it helps at this late stage.

gektron
  • 27
  • 3
  • Ok ignore the above. Goldmine is correct the longitude lines ARE equally spaced because of course Mercator projection is used for the maps. My problems with the solution stemmed from another minor bug in my application to do with subclassing the new iOS 7 MKTileOverlay class. – gektron Nov 11 '13 at 22:15
  • You may want to consider updating your post to reflect the information you included in your comment. – Derek Lee Jun 25 '15 at 02:49
1

Here, I put my answer and its working for swift 4.2.

MKMapView center and zoom in

Ashu
  • 3,373
  • 38
  • 34
  • 1
    If I click here and scroll down, and click on your link there, I will find myself here again and then I click here and now I'm stuck in an infinite loop – Matthijs May 29 '19 at 12:55
  • @Matthijs I have corrected the link. Please check and vote the answer. – Ashu May 31 '19 at 12:19
0

Based on quentinadam's answer

Swift 5.1

// size refers to the width/height of your tile images, by default is 256.0
// Seems to get better results using round()
// frame.width is the width of the MKMapView

let zoom = round(log2(360 * Double(frame.width) / size / region.span.longitudeDelta))
vicegax
  • 4,709
  • 28
  • 37
  • Thank you, looks good when the map is facing North. But what if you are rotating the map? What if pinch angle is different than 0? – pierre23 Feb 05 '20 at 19:28