Silver Challenge solution


#1

I created a working solution, though I feel a bit bloodied from banging against this challenge for awhile. All the changes happened in WhereamiViewController.m

Eventually I figured out that the various structures that hold location data could not be stored directly via NSUserDefaults; here’s how I set up the initial location defaults:

[code]+ (void)initialize
{
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];

[defaults setObject:[NSNumber numberWithInt:1] forKey:WhereamiMapTypePrefKey];
[defaults setObject:[NSNumber numberWithDouble:42.454859] forKey:WhereamiLastLatitudePrefKey];
[defaults setObject:[NSNumber numberWithDouble:-76.477504] forKey:WhereamiLastLongitudePrefKey];
    
[[NSUserDefaults standardUserDefaults] registerDefaults];

}[/code]

Figuring out where and when to record a new location in the NSUserDefaults took a little hunting too; I assumed it would be in the CLLocationManagerDelegate method locationManager:didUpdateToLocation:fromLocation: but for the life of me it seemed like that method was never being called; ultimately I settled on mapView:didUpdateUserLocation: as the place to update the location in NSUserDefaults:

mapView:didUpdateUserLocation:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:[NSNumber numberWithDouble:loc.latitude] forKey:WhereamiLastLatitudePrefKey]; [defaults setObject:[NSNumber numberWithDouble:loc.longitude] forKey:WhereamiLastLongitudePrefKey];

So, when to set the user defaults upon the launch of the App? I tried putting it into viewDidLoad where map type default is set, but my map would only load to a blank (blue) screen. After some instrumentation I discovered that just after viewDidLoad mapView:didUpdateUserLocation: was getting called with a location of 0,0; here’s some of the logging showing how, when I set the starting region in viewDidLoad, it was seemingly immediately lost to a mapView:didUpdateUserLocation: call with a bogus location:

[quote]2012-05-10 21:31:58.101 Whereami[92548:11603] viewDidLoad
2012-05-10 21:31:58.104 Whereami[92548:11603] starting latitude: 42.454859
2012-05-10 21:31:58.104 Whereami[92548:11603] starting longitude: -76.477504
2012-05-10 21:31:58.105 Whereami[92548:11603] setting starting region
2012-05-10 21:32:13.413 Whereami[92548:11603] mapView:didUpdateUserLocation
2012-05-10 21:32:16.152 Whereami[92548:11603] lat: 0.000000, lon: 0.000000[/quote]

Since setting the initial map region in viewDidLoad didn’t work, I moved it into the belly of the best (mapView:didUpdateUserLocation:), which - including the code to update to update NSUserDefaults with the latest location - ultimately looked like this:

[code]- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
CLLocationCoordinate2D loc = [userLocation coordinate];

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];    
if (loc.latitude == 0 && loc.longitude == 0) {
    loc = CLLocationCoordinate2DMake( 
                                     [defaults doubleForKey:WhereamiLastLatitudePrefKey],
                                     [defaults doubleForKey:WhereamiLastLongitudePrefKey]
                                     );
} else {
    [defaults setObject:[NSNumber numberWithDouble:loc.latitude] forKey:WhereamiLastLatitudePrefKey];
    [defaults setObject:[NSNumber numberWithDouble:loc.longitude] forKey:WhereamiLastLongitudePrefKey];
}

MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(loc, 250, 250);
[worldView setRegion:region animated:YES];

}
[/code]

So, though it works, I’m left with a couple uncertainties:

  1. Why didn’t I see calls to locationManager:didUpdateToLocation:fromLocation: with new location information?
  2. What the heck is with the calls to mapView:didUpdateUserLocation: with bogus 0,0 locations?

#2

Added the static variables for latitude and longitude:

NSString * const WhereamiLatitudePrefKey = @"WhereamiLatitudePrefKey"; NSString * const WhereamiLongitudePrefKey = @"WhereamiLongitudePrefKey";
Then created default coordinates in the initialize class method:

[code]+ (void)initialize
{
// Silver challenge - register a default value for coordinate: "The Mothership :)"
NSNumber *defaultMapType = [NSNumber numberWithInt:1];
NSNumber *defaultLatitude = [NSNumber numberWithDouble:37.331789];
NSNumber *defaultLongitude = [NSNumber numberWithDouble:-122.029620];

NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:
                          defaultLatitude, WhereamiLatitudePrefKey,
                          defaultLongitude, WhereamiLongitudePrefKey,
                          defaultMapType, WhereamiMapTypePrefKey,
                          nil];

[[NSUserDefaults standardUserDefaults] registerDefaults];
NSLog(@"%@", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);

}
[/code]
Load the coordinates in ViewDidLoad:

double savedLatitude = [[NSUserDefaults standardUserDefaults] doubleForKey:WhereamiLatitudePrefKey];
double savedLongitude = [[NSUserDefaults standardUserDefaults] doubleForKey:WhereamiLongitudePrefKey];
CLLocationCoordinate2D savedCoordinate = CLLocationCoordinate2DMake(savedLatitude, savedLongitude);
    
MKCoordinateRegion savedRegion = MKCoordinateRegionMakeWithDistance(savedCoordinate, 250, 250);
[worldView setRegion:savedRegion animated:YES];

Save the coordinates in foundLocation:

[[NSUserDefaults standardUserDefaults] setDouble:coord.latitude forKey:WhereamiLatitudePrefKey]; [[NSUserDefaults standardUserDefaults] setDouble:coord.longitude forKey:WhereamiLongitudePrefKey];
This line is helpful for logging your entire standardUserDefaults dictionary to the console:


#3

I put it in mapView:didUpdateUserLocation too, but I suppose it depends on the way you understood the instructions : if you think it should save the current location each time the GPS finds a new location, it should be in mapView:didUpdateUserLocation. If you think it should save the location when a BNRMapPoint is registered, then it should be in foundLocation: (which I believe is called only when you register a MapPoint).


#4

It’s funny because I’m doing the exact same thing as you guys, but the view is not visible to me until it updates it’s location once (which it updates to 0 0 and then ANOTHER default location). I am using an SSD … not sure if that should make a difference on the simulator side. Is this a simulator issue? It’s driving me nuts. If I comment out the default user updates… it does not update to 0. Hence I’m sure the issue happens here.

[quote]2012-09-11 15:40:14.825 Whereami[2447:c07] initialize
2012-09-11 15:40:14.827 Whereami[2447:c07] {

WhereamiMapLatitudePrefKey = "29.2029077";
WhereamiMapLongitudePrefKey = "-81.0499348";
WhereamiMapTypePrefKey = 1;

}
2012-09-11 15:40:14.875 Whereami[2447:c07] viewDidLoad
2012-09-11 15:40:14.876 Whereami[2447:c07] Loaded defaults - Latitude: 29.202908, Longitude: -81.049935
2012-09-11 15:40:14.911 Whereami[2447:c07] mapView:didUpdateUserLocation:
2012-09-11 15:40:14.911 Whereami[2447:c07] {

WhereamiMapLatitudePrefKey = 0;
WhereamiMapLongitudePrefKey = 0;
WhereamiMapTypePrefKey = 1;

}
2012-09-11 15:40:14.938 Whereami[2447:c07] mapView:didUpdateUserLocation:
2012-09-11 15:40:14.939 Whereami[2447:c07] {

WhereamiMapLatitudePrefKey = 0;
WhereamiMapLongitudePrefKey = 0;
WhereamiMapTypePrefKey = 1;

}
2012-09-11 15:40:16.483 Whereami[2447:c07] mapView:didUpdateUserLocation:
2012-09-11 15:40:16.484 Whereami[2447:c07] {

WhereamiMapLatitudePrefKey = "37.33233141";
WhereamiMapLongitudePrefKey = "-122.0312186";
WhereamiMapTypePrefKey = 1;

}

[/quote]

WhereamiViewController.m

[code]-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{ //changed to update the NSDefaults accordingly… saves defaults for the last found location
// Here we are… but how do we zoom?
CLLocationCoordinate2D loc = [userLocation coordinate];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(loc, 250, 250);
[worldView setRegion:region animated:YES];

[[NSUserDefaults standardUserDefaults] setDouble:loc.latitude forKey:WhereamiMapLatitudePrefKey];
[[NSUserDefaults standardUserDefaults] setDouble:loc.longitude forKey:WhereamiMapLongitudePrefKey];


NSLog(@"%@", NSStringFromSelector(_cmd));
NSLog(@"%@", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);

}
[/code]


#5

I’m only working with the simulator and I had to put my code inside of mapView:didUpdateUserLocation in the same fashion as @dmddmd. The catch is that when the app asks to use the User’s Location I said no. This was the only way I was able to get the app to zoom to the stored location. If I say yet to using the user’s location, pick a new location, give it a name, close the app, and then bring it back up, it will not zoom to the saved location. Instead, it zooms to the user’s location.

I wonder once I have the ability to deploy to my phone if this code will work better. Has anyone else had any issues with this challenge on the Simulator?