Iphone – MKMapView setRegion: odd behavior

iphone

I'm working on a 3-tabbed iPhone app., where I want each tab's view to look like it shares the same map. So at the moment, I'm just trying to figure out how to reset each view's MKMapView's region when one clicks on a new tab. [In other words, if you're on tab 1 and zoomed all the way in on a spot, then click on tab 2, I want it to show the same, zoomed in map.]

To do this, I have an MKCoordinateRegion in the app delegate that each view saves to before it goes away, and that each new view reads from before it is viewed.

[In other words, my app delegate is my TabBarControllerDelegate and I've overridden tabBarController: shouldSelectViewController:. This function is called after someone clicks on a tab button, and before the new tab's view's viewWillAppear: function is called. So, in this function, I save the region of the current view to my AppDelegate::MKCoordinateRegion variable, and then let the new view's viewWillAppear: (or viewDidLoad:) get called. Then, in the new view's viewWillAppear:, I get the MKCoordinateRegion from the app delegate, and assign it to my new view's region.]

However, the values from MKMapKit::setRegion: don't seem to be consistent and I don't know if I'm just doing something wrong, missing something entirely, or if something else is going on.

I've only been doing iPhone dev. (and Obj-c, etc.) for about 2 weeks now, so this very well just could be a newby mistake. And if so, I will go ahead and apologize now for wasting your time. Nonetheless, here's some code (and I've left in the comments I was using…just in case you try running it yourself):

iPhoneTestAppDelegate.h

...
@interface iPhoneTestAppDelegate : NSObject  
{
    UIWindow* _window;
    UITabBarController* _tabBarController;
    SubViewController* _subViewController;

    MKCoordinateRegion _mapRegion;
}

@property (nonatomic, retain) IBOutlet UIWindow* window;
@property (nonatomic, retain) IBOutlet UITabBarController* tabBarController;
@property (nonatomic, retain) IBOutlet SubViewController* subViewController;
@property (nonatomic, assign) MKCoordinateRegion mapRegion;

@end 

iPhoneAppDelegate.m

...
static CLLocationDegrees INITIAL_LATITUDE = 40.754019;
static CLLocationDegrees INITIAL_LONGITUDE = -73.973351;
static CLLocationDegrees INITIAL_SPAN_LAT_DEG = .10767;
static CLLocationDegrees INITIAL_SPAN_LONG_DEG = .109863;
...
- (void)applicationDidFinishLaunching:(UIApplication *)application 
{
    NSLog(@"Begin");
    // initialize the delegate's region
    MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
    region.center.latitude = INITIAL_LATITUDE;
    region.center.longitude = INITIAL_LONGITUDE;
    region.span.latitudeDelta = INITIAL_SPAN_LAT_DEG;
    region.span.longitudeDelta = INITIAL_SPAN_LONG_DEG;
    self.mapRegion = region;

    self.tabBarController.delegate = self;
    [self.window addSubview:self.tabBarController.view];
}

- (BOOL)tabBarController:(UITabBarController *)tabBarController
 shouldSelectViewController:(UIViewController *)viewController
{
 NSLog(@"in UITabBarControllerDelegate: shouldSelectViewController:");

 if([tabBarController.viewControllers indexOfObject:viewController] !=
  [tabBarController selectedIndex])
 {
  NSLog(@"different view selected");
  BaseViewController* currView = (BaseViewController *)[tabBarController selectedViewController];

  NSLog(@"%f %f %f %f", 
     self.mapRegion.center.latitude, self.mapRegion.center.longitude, 
     self.mapRegion.span.latitudeDelta, self.mapRegion.span.longitudeDelta);
  NSLog(@"%f %f %f %f", 
     currView.mapView.region.center.latitude, currView.mapView.region.center.longitude, 
     currView.mapView.region.span.latitudeDelta, currView.mapView.region.span.longitudeDelta);
  self.mapRegion = currView.mapView.region;
  NSLog(@"%f %f %f %f", 
     self.mapRegion.center.latitude, self.mapRegion.center.longitude, 
     self.mapRegion.span.latitudeDelta, self.mapRegion.span.longitudeDelta);
 }
 else
  NSLog(@"same view selected");

 return YES;
}

BaseViewController.m

...
- (void)viewDidLoad 
{
    [super viewDidLoad];
 NSLog(@"Base: viewDidLoad");

 iPhoneTestAppDelegate* appDelegate = (iPhoneTestAppDelegate *)
  [[UIApplication sharedApplication] delegate];
 [self.mapView setDelegate:self];
    //[self.mapView setRegion:appDelegate.mapRegion animated:YES];
 [self.mapView setRegion:[self.mapView regionThatFits:appDelegate.mapRegion]];

 NSLog(@"---------------------");
 NSLog(@"%f %f %f %f", 
    appDelegate.mapRegion.center.latitude, appDelegate.mapRegion.center.longitude, 
    appDelegate.mapRegion.span.latitudeDelta, appDelegate.mapRegion.span.longitudeDelta);
 NSLog(@"---------------------");
 NSLog(@"%f %f %f %f", 
    self.mapView.region.center.latitude, self.mapView.region.center.longitude, 
    self.mapView.region.span.latitudeDelta, self.mapView.region.span.longitudeDelta);
 NSLog(@"---------------------");  
}

- (void)viewWillAppear:(BOOL)animated
{
 [super viewWillAppear:animated];
 NSLog(@"Base: viewWillAppear");

 iPhoneTestAppDelegate* appDelegate = (iPhoneTestAppDelegate *)
  [[UIApplication sharedApplication] delegate];  
 //[self.mapView setRegion:appDelegate.mapRegion animated:YES];
 [self.mapView setRegion:[self.mapView regionThatFits:appDelegate.mapRegion]];

 NSLog(@"*********************");
 NSLog(@"%f %f %f %f", 
    appDelegate.mapRegion.center.latitude, appDelegate.mapRegion.center.longitude, 
    appDelegate.mapRegion.span.latitudeDelta, appDelegate.mapRegion.span.longitudeDelta);
 NSLog(@"*********************");
 NSLog(@"%f %f %f %f", 
    self.mapView.region.center.latitude, self.mapView.region.center.longitude, 
    self.mapView.region.span.latitudeDelta, self.mapView.region.span.longitudeDelta);
 NSLog(@"*********************");
}
...

Now, the first thing I notice when I run this is that when the initial view's viewDidLoad: is called, the delegate's region and my new view's region are the same (after I call setRegion:). However, after setRegion: is called in viewWillAppear:, my view's MKMapView's region is no longer equal to the delegates. Somehow, the exact same code in viewDidLoad: produces a different result when run in viewWillAppear:! Shouldn't that not be the case? Why does calling setRegion: from viewWillAppear: change the values of my view's MKMapView's region? Does this have something to do with regionThatFits: being called internally? I don't get it!

However, I kind of think that if I can figure that part out, I might be able to figure out the other issues; so, I won't go into more details here. But, if you make a quick app. with these functions and run it, you'll see the issues. Mainly, the view's maps are occasionally in synch, but as you zoom and move around (and keep switching back and forth between tabs), the span starts to shrink and each new tab press just keeps zooming me farther and farther out.

Note: I'm only running this on the simulator (3.0) [I don't have the hardware yet], so I don't know if that has anything to do with it. But, I kind of doubt it.

Anyway, thanks for any help you can offer. I really appreciate it.

Best Answer

I'm working on a similar issue with implementing a zoom-to-fit feature around a set of points. The only help I can give you at the moment is that yes, regionThatFits: is in fact being called internally. I'm calling setRegion with a region that precisely fits all the points, so that there should be a point lying exactly on the edge of every side, along with points inside the viewable area. The region that the mapview actually has after the setRegion call is different, but precisely the same as the region you get if you call regionThatFits: with the region I want to be using. This region is usually somewhat larger than what I want.

I've been working on it under the theory that regionThatFits is merely trying to capture the aspect ratio, and is bungling the aspect ratio correction. However, two things seem to indicate this is not the only thing going on: first, the aspect ratio of the region returned by regionThatFits is not the same as the aspect ratio of the map view. Second, when I use my own aspect correction code to get the exact same aspect ratio that regionThatFits is outputting, I still get the erroneously large region out of regionThatFits (and the mapView).

So far, MapKit is pretty infuriating.