4

We've been having trouble with the MKMapView's region in iOS7.

After our app has started and the device has changed UI orientation a couple of times, the values returned by map.region become very strange. They tend to have a sensible longitude span but a small latitude span, or vice-versa, as though the map were under the impression that its bounds had been cut down to a narrow rectangle along one edge of the screen. The actual bounds and frame of the MKMapView are still sensible after this has happened.

It's been possible to work round some of the problems this causes by calculating a region of our own from the map's actual bounds, but we still have a couple of issues that we can't fix. For example, when an annotation is tapped to bring up its callout, the map will sometimes pan to move the callout into that small part of the screen which it thinks it occupies.

Has anyone else experienced this problem ?

Our shims to implement the workaround are below, in case they are useful:

+(void)setMap: (MKMapView*) map region:(MKCoordinateRegion) region
{


CGRect realBounds = map.bounds;

MKCoordinateRegion claimedRegion = map.region; // the map's claimed region, which is wonkily different after a rotate in ios7

CGRect claimedBounds = [map convertRegion:claimedRegion toRectToView:map]; // the bounds which the map thinks its region occupies


// if we want region to map to realBounds, but the map thinks it is only claimedBounds big, what
// reduced region will map to claimedBounds ?

MKCoordinateRegion reducedRegion = [Utilities sliceRegion: region inBounds: realBounds toReducedBounds: claimedBounds];

[map setRegion:reducedRegion animated:YES];

}

+ (MKCoordinateRegion) sliceRegion: (MKCoordinateRegion) bigRegion inBounds: (CGRect) wholeBounds toReducedBounds: (CGRect) reducedBounds
{
MKCoordinateRegion reducedRegion;


 // Coords of our region's corners in lat/long
CLLocationDegrees left = bigRegion.center.longitude - bigRegion.span.longitudeDelta/2.0;
CLLocationDegrees right = bigRegion.center.longitude + bigRegion.span.longitudeDelta/2.0;
CLLocationDegrees top = bigRegion.center.latitude + bigRegion.span.latitudeDelta/2.0;
CLLocationDegrees bottom = bigRegion.center.latitude - bigRegion.span.latitudeDelta/2.0;


// Coords of our bounds in pixels
CGFloat wholeLeft = wholeBounds.origin.x;
CGFloat wholeRight = wholeBounds.origin.x + wholeBounds.size.width;
CGFloat wholeTop = wholeBounds.origin.y;
CGFloat wholeBottom = wholeBounds.origin.y + wholeBounds.size.height;

// Coords of the smaller bounds in pixels
CGFloat reducedLeft = reducedBounds.origin.x;
CGFloat reducedRight = reducedBounds.origin.x + reducedBounds.size.width;
CGFloat reducedTop = reducedBounds.origin.y;
CGFloat reducedBottom = reducedBounds.origin.y + reducedBounds.size.height;

// Now work out what the lat & long values for the corners of the reduced bounds are
CLLocationDegrees newLeft = left + (right-left) * (reducedLeft - wholeLeft) / (wholeRight - wholeLeft);
CLLocationDegrees newRight = left + (right-left) * (reducedRight - wholeLeft) / (wholeRight - wholeLeft);
CLLocationDegrees newTop = top + (bottom - top) * (reducedTop - wholeTop) / (wholeBottom - wholeTop);
CLLocationDegrees newBottom = top + (bottom - top) * (reducedBottom - wholeTop) / (wholeBottom - wholeTop);

reducedRegion.center.longitude = (newRight + newLeft) / 2.0;
reducedRegion.center.latitude = (newBottom + newTop) / 2.0;

reducedRegion.span.longitudeDelta = newRight - newLeft;
reducedRegion.span.latitudeDelta = newTop - newBottom;


return reducedRegion;
}


+(MKCoordinateRegion)getMapRegion: (MKMapView*) map
{


CGRect bounds = map.bounds;

MKCoordinateRegion region = [map convertRect:bounds toRegionFromView:map]; // the region we can see on the screen, not the map's wonky region!

if ((region.span.latitudeDelta < 0.0) || (region.span.longitudeDelta < 0.0) ||  region.span.longitudeDelta / region.span.latitudeDelta > 5.0 || region.span.latitudeDelta / region.span.longitudeDelta > 5.0 )
{
    LogD(@"getMap: region: bad span -  lat: %f, long: %f", region.span.latitudeDelta, region.span.longitudeDelta);
}

return region;
}

+(void)setMap: (MKMapView*) map center: (CLLocationCoordinate2D) center
{


CGRect bounds = map.bounds;

MKCoordinateRegion boundsRegion = [map convertRect:bounds toRegionFromView:map]; // the region we can see on the screen

MKCoordinateRegion claimedRegion = map.region; // the map's claimed region, which is wonkily different after a rotate in ios7

CLLocationCoordinate2D offsetCenter; // make up a value to tell the map to center on which will make it really center

offsetCenter.latitude = center.latitude - ( boundsRegion.center.latitude - claimedRegion.center.latitude );
offsetCenter.longitude = center.longitude - ( boundsRegion.center.longitude - claimedRegion.center.longitude );

[map setCenterCoordinate:offsetCenter animated:YES];


}


+(CLLocationCoordinate2D)getMapCenter: (MKMapView*) map
{

CGRect bounds = map.bounds;

MKCoordinateRegion boundsRegion = [map convertRect:bounds toRegionFromView:map]; // the region we can see on the screen
return boundsRegion.center;

}
Richard Sewell
  • 101
  • 1
  • 5
  • Have you tried to run you map view in full screen? That means without a status bar and without a navigation and tool bar? I case of the translucent bars from iOS 7 the MKMapView does some magic calculations regarding the layout guides. See my answer to this question: http://stackoverflow.com/questions/18903808/ios7-compass-in-mapview-placing/18904200#18904200 – Klaas Sep 25 '13 at 22:34
  • Thanks for that. I don't think it's our issue, because we don't see our problem till after the portrait-landscape transition has happened a couple of times. – Richard Sewell Sep 26 '13 at 12:54
  • Keep in mind that the topLayoutGuide changes when rotating, because the navigation bar changes its height. I found out that even when you change the visibility of the status bar the coordinate center of the map moves. – Klaas Sep 26 '13 at 16:35
  • Thanks - that has helped us sort out a centering problem. But it's very different from the more general map region bug we started with, which is less predictable and causes a much larger centering error. – Richard Sewell Oct 02 '13 at 08:34

1 Answers1

0

When I set the map region on iOS 7 in LandscapeRight everything works fine.

When I rotate the device to LandscapeLeft and load the same region the map shifts massively and is zoomed way to far in. zoom level needs to be multiplied by 100 to fix the issue ie. 50000 becomes 5000000 & I need to subtract 23 from the lat & add 3 to the lon ie. (41.0, 29.0) becomes (18.0, 32.0).

After some testing I am able to fix the issue like so for iOS 7 & iOS 6 (excuse the iOS version check its quick and dirty)

if([[[UIDevice currentDevice] systemVersion] rangeOfString:@"7."].location != NSNotFound){
        if(self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft){
            CLLocationCoordinate2D startCoord = CLLocationCoordinate2DMake(41.0, 29.0);
            [_mapView setRegion:MKCoordinateRegionMakeWithDistance(startCoord, 5000000.0, 5000000.0) animated:NO];
        }else if(self.interfaceOrientation == UIInterfaceOrientationLandscapeRight){
            CLLocationCoordinate2D startCoord = CLLocationCoordinate2DMake(18.0, 32.0);
            [_mapView setRegion:MKCoordinateRegionMakeWithDistance(startCoord, 50000.0, 50000.0) animated:NO];
        }

    }else{
        MKCoordinateRegion region;
        region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.3;
        region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.7;
        region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * span; // Add a little extra space on the sides
        region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * span; // Add a little extra space on the sides

        region = [_mapView regionThatFits:region];
        [_mapView setRegion:region animated:NO];
    }
damien murphy.
  • 371
  • 2
  • 16