Setting the zoom level for a MKMapView
Asked Answered
M

15

129

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

Malvasia answered 15/11, 2010 at 22:46 Comment(0)
D
221

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)
Devonadevondra answered 17/7, 2012 at 9:35 Comment(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.Ichthyolite
Nice answer. However the zoom will be different depending on the screen size, no?Referendum
Interestingly, MKCoordinateRegionMakeWithDistance is still around in Swift. This solution works!Necromancy
S
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
Scottyscotus answered 22/2, 2013 at 9:14 Comment(11)
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]; }Scornik
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.Scottyscotus
+1 for the unobtrusive way of adding this functionality via a categoryThumbprint
What is the reverse of that formula, to work out the current zoom level?Helvetia
I think it is this: double z = log2(360 * ((self.mapView.frame.size.width/256) / self.mapView.region.span.longitudeDelta));Helvetia
Added updated answer, inspired by Adil's work - Thank you for this!! So much simpler than most other methods. https://mcmap.net/q/173522/-setting-the-zoom-level-for-a-mkmapviewHelvetia
Can someone please explain to me what the 256 represents?Aisne
@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.Scottyscotus
You add +1 to get the zoomLevel, so maybe you should remove 1 too when calculating the span, nop ?Referendum
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.Scottyscotus
If I set the zoomLevel 14 then getZoomLevel returns 15. I think that +1 is not needed.Diacid
G
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

Giro answered 15/11, 2010 at 22:49 Comment(3)
wow, its a lot of code, u would think it should be built in. thanks. will have a look hows its done.Malvasia
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!Giro
Didnt work for me, it just displays the same zoom level as before. I must be doing something wrong.Malvasia
I
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];
Interradial answered 15/11, 2010 at 23:12 Comment(4)
This made no difference for me, when I change some values, it just doesnt load the map.Malvasia
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.Interradial
I set it before runtime. I tested values above and below 1.Malvasia
Good answer but try to change latitude, longitude delta to 0.1 - it is zoomed more.Syndesmosis
S
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)
    }
}
Sherman answered 3/5, 2015 at 1:35 Comment(3)
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.5Gynaeco
some comments on this would be more helpful It is working in Swift 3.Cotter
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 -= 2Keratin
V
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.

Valli answered 11/8, 2015 at 7:12 Comment(0)
H
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.

Harquebusier answered 23/11, 2016 at 9:11 Comment(1)
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?Militia
H
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
Helvetia answered 14/12, 2013 at 23:36 Comment(0)
E
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.

Edy answered 29/11, 2012 at 11:21 Comment(0)
S
3

Swift:

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

locValue is your coordinate.

Speaker answered 23/12, 2018 at 14:12 Comment(0)
S
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()
Servitude answered 16/10, 2015 at 6:15 Comment(0)
S
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)
    }
}
Scheers answered 24/6, 2020 at 14:56 Comment(0)
D
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.

Deoxyribonuclease answered 9/11, 2013 at 10:59 Comment(2)
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.Deoxyribonuclease
You may want to consider updating your post to reflect the information you included in your comment.Forfeit
G
1

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

MKMapView center and zoom in

Glacial answered 5/4, 2019 at 11:43 Comment(2)
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 😏Floozy
@Floozy I have corrected the link. Please check and vote the answer.Glacial
T
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))
Tarah answered 29/8, 2019 at 9:10 Comment(1)
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?Ritualism

© 2022 - 2024 — McMap. All rights reserved.