3
votes

I have a simple iphone app that uses the MKMapView. It has a segmented control that will adjust the region to zoom (street, neighborhood, city, state, world). It works OK but if I zoom out to state level and back to street, I notice that the center point has shifted.

The following are two delegate methods that get called before and after the region changes and my changeZoom method that gets invoked by the segmented control:

- (void)mapView:(MKMapView *)mView regionDidChangeAnimated:(BOOL)animated
{
 MKCoordinateRegion region = [mapView region];

 NSLog(@"Region DID change.   Center is now %f,%f,  Deltas=%f,%f", 
    region.center.latitude, region.center.longitude,
    region.span.latitudeDelta, region.span.longitudeDelta);
}

- (void)mapView:(MKMapView *)mView regionWillChangeAnimated:(BOOL)animated
{
 MKCoordinateRegion region = [mapView region];

 NSLog(@"Region WILL change.   Center is now %f,%f,  Deltas=%f,%f", 
    region.center.latitude, region.center.longitude,
    region.span.latitudeDelta, region.span.longitudeDelta);
}


- (IBAction)changeZoom:(id)sender
{
 UISegmentedControl *sc = (UISegmentedControl *)sender;

 float mile_range = 1;

 NSLog(@"changeZoom called.   MapView currently is Center=%f,%f  Deltas=%f,%f", 
    mapView.region.center.latitude, mapView.region.center.longitude,
    mapView.region.span.latitudeDelta, mapView.region.span.longitudeDelta );

 switch ( [sc selectedSegmentIndex] )
 {
  case 0: /* Street */
   mile_range=1;
   break;
  case 1: /* Hood */
   mile_range=2;
   break;
  case 2: /* City */
   mile_range=30;
   break;
  case 3: /* State */
   mile_range=500;
   break;
  case 4: /* World */
   mile_range=4000;
   break;
  default: /* Error */
   NSLog(@"Unknown segment selected from the UISegmentedControl!!!!!!!!!!!!!!");
   return;
   break;
 }

 originalZoomCenter = mapView.region.center;
 MKCoordinateRegion region;
    region = MKCoordinateRegionMakeWithDistance( originalZoomCenter, mile_range/MILES_PER_METER, mile_range/MILES_PER_METER );
    region = [mapView regionThatFits: region];

 NSLog(@"Calling setRegion Center=%f,%f  Deltas=%f,%f", 
    region.center.latitude, region.center.longitude,
    region.span.latitudeDelta, region.span.longitudeDelta );

    [mapView setRegion: region animated: YES];

 NSLog(@"After setRegion Center=%f,%f  Deltas=%f,%f", 
    mapView.region.center.latitude, mapView.region.center.longitude,
    mapView.region.span.latitudeDelta, mapView.region.span.longitudeDelta );
}

If I run the app and it starts out at street zoom level on my current location and then I click to zoom out to state level and back to street level, I get the following output in the console:

Region WILL change.   Center is now 30.145127,-40.078125,  Deltas=151.851332,225.000000
Region DID change.   Center is now 37.331676,-122.030725,  Deltas=0.024842,0.027466
changeZoom called.   MapView currently is Center=37.331676,-122.030725  Deltas=0.024842,0.027466
Calling setRegion Center=37.331676,-122.030725  Deltas=12.708100,14.062500
Region WILL change.   Center is now 37.331676,-122.030725,  Deltas=0.024842,0.027466
After setRegion Center=37.331676,-122.030725  Deltas=0.024842,0.027466
Region DID change.   Center is now 37.335224,-122.036133,  Deltas=12.707505,14.062500 
changeZoom called.   MapView currently is Center=37.335224,-122.036133  Deltas=12.707505,14.062500
Calling setRegion Center=37.335224,-122.036133  Deltas=0.024841,0.027466
Region WILL change.   Center is now 37.335224,-122.036133,  Deltas=12.707505,14.062500
After setRegion Center=37.335224,-122.036133  Deltas=12.707505,14.062500
Region DID change.   Center is now 37.335224,-122.036133,  Deltas=0.024841,0.027466

So all the calls I make in my code proceed as expected and the center does not change but when I monitor the MKMapViewDelegate method regionDidChangeAnimated, I see that the center gets moved. If I set a breakpoint there, I see a stack that looks like this:

#0 0x00034c4c in -[MapViewController mapView:regionDidChangeAnimated:] at MapViewController.m:209
#1 0x01fbf7bf in -[MKMapView _didChangeRegionMidstream:]
#2 0x003e11f5 in -[UIView(Gestures) animator:stopAnimation:]
#3 0x003743be in -[UIAnimator stopAnimation:]
#4 0x00374154 in -[UIAnimator(Static) _advance:]
#5 0x0264d719 in HeartbeatTimerCallback
#6 0x01d28ac0 in CFRunLoopRunSpecific
#7 0x01d27c48 in CFRunLoopRunInMode
#8 0x0264a78d in GSEventRunModal
#9 0x0264a852 in GSEventRun
#10 0x00307003 in UIApplicationMain
#11 0x00002194 in main at main.m:14

This doesn't really help me understand why the center would be moving. I have run this both in the simulator and on a device and it is driving me a bit crazy.

If anyone has any insight as to what is causing the center to shift, I would appreciate any pointers.

1
The errors I am seeing when I zoom from 1 mile deltas to: City (30 miles) error is .12 miles (0.4%) State (500 miles) error is .78 miles (0.156%) World (1000 miles) error is 3.17 miles (0.317%) So the center is drifting by a fraction of a percent when I zoom out to these long/lat deltas and back in. I wonder if this is to be expected as floating-point truncation might be happening internally within the MKMapView class ??Pat

1 Answers

3
votes

From my experience, the region property of a map is re-computed after its region is modified either programmatically with the public APIs, or with a UI event.

By zooming out, you loose precision on the center of the map (one pixel covers a larger area). Then, after setting a region with setRegion:[animated:], the (thus computed) map.region can be different than the region that was given in setRegion. This could explain the deltas you get.

To solve your particular problem, I would rather store the center of the map each time the user move the map and use the stored value to do the zooming instead of trusting map.region.center.