Chris Prince
Chris Prince

Reputation: 7574

MKMapView reset back to world view: Rebaked

I have been having ongoing memory leak problems with MKMapView (reported to Apple, but no response), and so as a work-around I tried to just allocate one MKMapView and re-use that map instead of reallocating a new one. The problem I'm running into with this is I have yet to be able to reset the MKMapView to the view it starts with. I assume because I'm working from the USA, I initially get a view like this:

map of USA
(source: cprince.com)

With this code:

static MKMapView *map = nil;

- (void) showUserLocation {    
    [map setShowsUserLocation:YES];

    CLLocationCoordinate2D coord;

    coord.latitude = 39.0; // changed from original coords
    coord.longitude = -105.0;

    MKCoordinateRegion r = MKCoordinateRegionMakeWithDistance(coord, 1000, 1000);

    [map setRegion:r animated:YES];
}

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    if (! map) {
        CGRect r = CGRectMake(20, 50, 196, 75);
        map = [[MKMapView alloc] initWithFrame:r];

        defaultRegion = MKCoordinateRegionMake(map.centerCoordinate, MKCoordinateSpanMake(180, 360));
    } else {
        [map setRegion:defaultRegion animated:YES];
    }

    [self.view addSubview:map];
}

- (IBAction)annotateMap:(id)sender {
    [self showUserLocation];
}

when I click on the button that calls annotateMap, I get the following map (no surprises yet):

map of my home
(source: cprince.com)

then, when I navigate away (using a navigation controller) from this view and then re-enter the view (viewWillAppear is again called, but this time it re-uses the MKMapView previously allocated), I get the following map view:

map of ocean
(source: cprince.com)

and of course I should be getting the original default region because of this part of the viewWillAppear:

        defaultRegion = MKCoordinateRegionMake(map.centerCoordinate, MKCoordinateSpanMake(180, 360));
    } else {
        [map setRegion:defaultRegion animated:YES];
    }

It doesn't matter if do the setRegion after the addSubview or before. That is, the change below doesn't correct the probelem:

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    BOOL firstTime = NO;
    if (! map) {
        firstTime = YES;
        CGRect r = CGRectMake(20, 50, 196, 75);
        map = [[MKMapView alloc] initWithFrame:r];

        defaultRegion = MKCoordinateRegionMake(map.centerCoordinate, MKCoordinateSpanMake(180, 360));
    }

    [self.view addSubview:map];
    if (! firstTime) {
        [map setRegion:defaultRegion animated:YES];
    }
}

and using MKMapRectWorld doesn't help either:

defaultRegion = MKCoordinateRegionForMapRect(MKMapRectWorld);

The problem is also not due to some kind of race condition with MKMapView. If I alter the handler for the button:

- (IBAction)annotateMap:(id)sender {
    defaultRegion = MKCoordinateRegionMake(map.centerCoordinate, MKCoordinateSpanMake(180, 360));
    [self showUserLocation];
}

and wait until the map is settled on the USA view before I click the button, the problem is the same.

I am using iOS 6.1.2 and XCode 4.6.

(The full XCode project is available at http://www.cprince.com/stackoverflow/Map.zip).

Thoughts?

FIRST CHANGE

I made the defaultRegion a static instead of an instance variable and this doesn't change the situation markedly. (I reported the lat/long for the default region below). Here's the result in the map:

map of ocean
(source: cprince.com)

For clarity, the code as it stands now is:

static MKMapView *map = nil;
static MKCoordinateRegion defaultRegion;

- (void) showUserLocation {    
    [map setShowsUserLocation:YES];

    CLLocationCoordinate2D coord;
    coord.latitude = 39.0; // changed from original
    coord.longitude = -105.0;

    MKCoordinateRegion r = MKCoordinateRegionMakeWithDistance(coord, 1000, 1000);

    [map setRegion:r animated:YES];
}

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    BOOL firstTime = NO;
    if (! map) {
        firstTime = YES;
        CGRect r = CGRectMake(20, 50, 196, 75);
        map = [[MKMapView alloc] initWithFrame:r];

        defaultRegion = MKCoordinateRegionMake(map.centerCoordinate, MKCoordinateSpanMake(180, 360));

        NSLog(@"defaultRegion: center: lat: %f, long: %f; span: %f dlat: dlong: %f", defaultRegion.center.latitude, defaultRegion.center.longitude, defaultRegion.span.latitudeDelta, defaultRegion.span.longitudeDelta);
        //defaultRegion = MKCoordinateRegionForMapRect(MKMapRectWorld);
    }

    [self.view addSubview:map];
    if (! firstTime) {
        NSLog(@"defaultRegion: center: lat: %f, long: %f; span: %f dlat: dlong: %f", defaultRegion.center.latitude, defaultRegion.center.longitude, defaultRegion.span.latitudeDelta, defaultRegion.span.longitudeDelta);
        [map setRegion:defaultRegion animated:YES];
    }
}

- (IBAction)annotateMap:(id)sender {
    //defaultRegion = MKCoordinateRegionMake(map.centerCoordinate, MKCoordinateSpanMake(180, 360));
    [self showUserLocation];
}

Upvotes: 3

Views: 1552

Answers (1)

Rob
Rob

Reputation: 437632

If you make your view controller a delegate for the MKMapView and implement regionDidChangeAnimated, you'll see that when you first create a map view, the centerCoordinate is 30, -40, but it's subsequently set to 37.178181, -96.054581 (for U.S. users, at least). You apparently can't rely upon the region and/or centerCoordinate immediately after creating the map view. You have to give it a chance to reset its region, which apparently happens asynchronously.

If you want to save the defaultRegion after the MKMapView sets it the first time, set the delegate property of the MKMapView and then implement:

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    static BOOL firstTime = YES;
    if (firstTime)
    {
        defaultRegion = mapView.region;
        firstTime = NO;
    }
}

Note, on viewWillDisappear, since you've made your map a static, make sure to nil the map's delegate (since that old view controller will disappear). If you want to continue to have your new instance of the view controller to be a MKMapViewDelegate, then just set the delegate again when you re-add the map view to your new instance of your detail view controller.


Old answer:

When you set defaultRegion, that's an instance variable. Thus, that's lost when you dismiss the view. When you come back to the map (i.e. firstTime is NO), the defaultRegion is never initialized, and thus is 0,0, and thus is in the middle of the Atlantic Ocean.

You're obviously not saving the defaultRegion (generally you'd have a protocol to pass that back to the master view controller), which makes it hard for the app to know what to do when you select the item in the master view again.

Upvotes: 3

Related Questions