MKMapView setRegion animated does not show animation

I also had this problem where it would not always animate, sometimes it would just jump and dissolve instead. However, I noticed that if you animate the camera instead of the region, it consistently animates.

But using the camera, you have to set the eye distance/altitude instead of the lat/lon span. I have a simple calculation for that below which is rough, it basically just sets the altitude (in meters) to the same number of meters as the longitude span of the region. If you wanted exact accuracy you'd have to figure out the number of meters per degree for the region's latitude, which changes slightly because the earth is not a perfect sphere. Of course you could multiply that value to widen or narrow the view to taste.

Swift 4.1 example code:

/* -------------------------------------------------------------------------- */
func animateToMapRegion(_ region: MKCoordinateRegion) {
    // Quick and dirty calculation of altitude necessary to show region.
    // 111 kilometers per degree longitude.
    let metersPerDegree: Double = 111 * 1_000
    let altitude = region.span.longitudeDelta * metersPerDegree

    let camera = MKMapCamera(lookingAtCenter: region.center, fromEyeCoordinate: region.center, eyeAltitude: altitude)

    self.mapView.setCamera(camera, animated: true)
}

I've tested this issue with a simple demo application on iOS 6 and iOS 7 beta. It turns out that the map view actually not always animates the transition between regions. It depends on how far the regions lay apart. For example a transition from Paris to London is not animated. But if you first zoom out a little bit and then go to London it will be animated.

The documentation says:

animated: Specify YES if you want the map view to animate the transition to the new region or NO if you want the map to center on the specified region immediately.

But as we've seen, we can not rely on the animation. We can only tell the map view that the transition should be animated. MapKit decides whether an animation is appropriate. It tells the delegate if the transition will be animated in -(void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated.

In order to consistently animate the region change in all cases you will need to animate to a intermediate region first. Let A be the current map region and B the target region. If there is an intersection between the regions you can transition directly. (Transform the MKCoordinateRegion to an MKMapRect and use MKMapRectIntersection to find the intersection). If there is no intersection, calculate a region C that spans both regions (use MKMapRectUnion and MKCoordinateRegionForMapRect). Then first go to to region C and in regionDidChangeAnimated go to region B.

Sample code:

MKCoordinateRegion region = _destinationRegion;    
MKMapRect rect = MKMapRectForCoordinateRegion(_destinationRegion);
MKMapRect intersection = MKMapRectIntersection(rect, _mapView.visibleMapRect);
if (MKMapRectIsNull(intersection)) {
    rect = MKMapRectUnion(rect, _mapView.visibleMapRect);
    region = MKCoordinateRegionForMapRect(rect);
    _intermediateAnimation = YES;
}
[_mapView setRegion:region animated:YES];

-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    if (_intermediateAnimation) {
        _intermediateAnimation = NO;
        [_mapView setRegion:_destinationRegion animated:YES];
    }
}

This helper method is taken from here

MKMapRect MKMapRectForCoordinateRegion(MKCoordinateRegion region)
{
    MKMapPoint a = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
                                                                      region.center.latitude + region.span.latitudeDelta / 2,
                                                                      region.center.longitude - region.span.longitudeDelta / 2));
    MKMapPoint b = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
                                                                      region.center.latitude - region.span.latitudeDelta / 2,
                                                                      region.center.longitude + region.span.longitudeDelta / 2));
    return MKMapRectMake(MIN(a.x,b.x), MIN(a.y,b.y), ABS(a.x-b.x), ABS(a.y-b.y));
}

The WWDC 2013 session 309 Putting Map Kit in Perspective explains how to do such complex transitions in iOS 7.


You simply have to get your current location and then call this function:

import MapKit

var appleMapView = MKMapView()

var currentCoordinate: CLLocationCoordinate2D?

currentCoordinate must be your current location coordinates:

 ◙ if let currentLoc = self.currentCoordinate {
        let center = CLLocationCoordinate2D(latitude: currentLoc.latitude, longitude: currentLoc.longitude)
        let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
        appleMapView.setRegion(region, animated: true)
    }

Here are the functions by @Felix rewritten to Swift 4:

// MARK: - MapView help properties
var destinationRegion: MKCoordinateRegion?
var intermediateAnimation = false

func center() {
    // Center map
    var initialCoordinates = CLLocationCoordinate2D(latitude: 49.195061, longitude: 16.606836)
    var regionRadius: CLLocationDistance = 5000000

    destinationRegion = MKCoordinateRegionMakeWithDistance(initialCoordinates, regionRadius * 2.0, regionRadius * 2.0)
    centreMap(on: destinationRegion!)
}


private func mapRect(forCoordinateRegion region: MKCoordinateRegion) -> MKMapRect {
    let topLeft = CLLocationCoordinate2D(latitude: region.center.latitude + (region.span.latitudeDelta/2), longitude: region.center.longitude - (region.span.longitudeDelta/2))
    let bottomRight = CLLocationCoordinate2D(latitude: region.center.latitude - (region.span.latitudeDelta/2), longitude: region.center.longitude + (region.span.longitudeDelta/2))

    let a = MKMapPointForCoordinate(topLeft)
    let b = MKMapPointForCoordinate(bottomRight)

    return MKMapRect(origin: MKMapPoint(x:min(a.x,b.x), y:min(a.y,b.y)), size: MKMapSize(width: abs(a.x-b.x), height: abs(a.y-b.y)))
}


func centreMap(on region: MKCoordinateRegion) {
    var region = region
    var rect = mapRect(forCoordinateRegion: region)
    let intersection = MKMapRectIntersection(rect, mapView.visibleMapRect)

    if MKMapRectIsNull(intersection) {
        rect = MKMapRectUnion(rect, mapView.visibleMapRect)
        region = MKCoordinateRegionForMapRect(rect)
        intermediateAnimation = true
    }

    mapView.setRegion(region, animated: true)
}

// MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if intermediateAnimation, let region = destinationRegion {
        intermediateAnimation = false
        mapView.setRegion(region, animated: true)
    }
}