Gold/Silver Solution to Challenge


#1

Here is my solution to this challenge, it works, and does seem to do what is required

RogMapPoint.h

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface RogMapPoint : NSObject <MKAnnotation>
{
    
}
// A new designated initializer for instances of RogMapPoint
- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t;

// This is a required property from MKAnnotation
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

// This is an optional property from MKAnnotation
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

@end

RogMapPoint.m

#import "RogMapPoint.h"

@implementation RogMapPoint

@synthesize coordinate, title, subtitle;

- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
    self = [super init];
    if (self) {
        coordinate = c;
        [self setTitle:t];
        subtitle = [NSDateFormatter localizedStringFromDate:[NSDate date] 
                                                  dateStyle:NSDateFormatterMediumStyle 
                                                  timeStyle:NSDateFormatterShortStyle];

    }
    return self;
}

- (id)init
{
    return [self initWithCoordinate:CLLocationCoordinate2DMake(43.07, -89.32) 
                              title:@"Home Town"];
}

@end

WhereamiViewController.h

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>


@interface WhereamiViewController : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate, UITextFieldDelegate>
{
    CLLocationManager *locationManager;
    
    IBOutlet MKMapView *worldView;
    IBOutlet UIActivityIndicatorView *activityIndicator;
    IBOutlet UITextField *locationTitleField;
    
    IBOutlet UISegmentedControl *mapViewType;
}

- (void)findLocation;
- (void)foundLocation:(CLLocation *)loc;

- (IBAction)changeMapViewType:(id)sender;

//- (void)doSomethingWeird;

@end

WhereamiViewController.m

#import "WhereamiViewController.h"
#import "RogMapPoint.h"

@interface WhereamiViewController ()

@end

@implementation WhereamiViewController

- (void)findLocation
{
    [locationManager startUpdatingLocation];
    [activityIndicator startAnimating];
    [locationTitleField setHidden:YES];
}

- (void)foundLocation:(CLLocation *)loc
{
    CLLocationCoordinate2D coord = [loc coordinate];
    
    // Create an instance of RogMapPoint with the current data
    RogMapPoint *mp = [[RogMapPoint alloc] initWithCoordinate:coord 
                                                       title:[locationTitleField text]];
    // Add it to the map view
    [worldView addAnnotation:mp];
    
    // Zoom the region to this location
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coord, 1000, 1000);
    [worldView setRegion:region 
                animated:YES];
    
    // Reset the UI
    [locationTitleField setText:@""];
    [activityIndicator stopAnimating];
    [locationTitleField setHidden:NO];
    [locationManager stopUpdatingLocation];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    
    if (self) {
        // Create location manager object
        locationManager = [[CLLocationManager alloc] init];
        
        // There will be a warning from this line of code; ignore it for now
        [locationManager setDelegate:self];
        
        //[self doSomethingWeird];
        
        // And we want it to be as accurate as possible
        // regardless of how much time/power it takes
        [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
        
        // Tell our manager to start looking for its location immediately
        // [locationManager startUpdatingLocation];
    }
    
    return self;
}

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation
{
    NSLog(@"%@", newLocation);
    
    // How many seconds ago was this new location created?
    NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow];
    
    // CLLocationManagers will return the last found location of the 
    // device first, you dont want that data in this case.
    // If this location was made more than 3 minutes ago, ignore it.
    if (t < -180) {
        // This is cached data, you don't want it, keep looking
        return;
    }
    
    [self foundLocation:newLocation];
}

- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error
{
    NSLog(@"Could not find location: %@", error);
}
/*
- (void)doSomethingWeird
{
    NSLog(@"Line 1");
    NSLog(@"Line 2");
    NSLog(@"Line 3");
}*/

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    // This method isn't implemented yet - but will be soon.
    [self findLocation];
    
    [textField resignFirstResponder];
    
    
    return YES;
}

- (void)mapView:(MKMapView *)mapView 
didUpdateUserLocation:(MKUserLocation *)userLocation
{
    // here we are but how do we actually zoom?
    /* below is how its done in the book
    CLLocationCoordinate2D loc = [userLocation coordinate];
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(loc, 1000, 1000);
    [worldView setRegion:region animated:YES]; */
    
    // below is my short form and does the same job.
    [worldView setRegion:MKCoordinateRegionMakeWithDistance([userLocation coordinate], 1000, 1000) animated:YES];
}

- (void)viewDidLoad
{
    [worldView setShowsUserLocation:YES];
}

- (IBAction)changeMapViewType:(id)sender
{
    NSLog(@"enterring method changeMapViewType");
    int maptype = [sender selectedSegmentIndex];
    
    if (maptype == 0) {
        
        NSLog(@"standard view");
        [worldView setMapType:MKMapTypeStandard];

    } else if (maptype == 1) {

        NSLog(@"hybrid view");
        [worldView setMapType:MKMapTypeHybrid];
        
    } else if (maptype == 2) {
        
        NSLog(@"satellite view");
        [worldView setMapType:MKMapTypeSatellite];

    }
    
    /* an alternative way using the "switch" statement
     
    NSLog(@"enterring method changeMapViewType");
    switch ([sender selectedSegmentIndex]) {
        case 0: {
            NSLog(@"standard view");
            [worldView setMapType:MKMapTypeStandard];
            break;
        }
        case 1: {
            NSLog(@"hybrid view");
            [worldView setMapType:MKMapTypeHybrid];
            break;
        }
        case 2: {
            NSLog(@"satellite view");
            [worldView setMapType:MKMapTypeSatellite];
            break;
        }
    } */
}

- (void)dealloc
{
    // Tell the location manager to stopp sending us messages
    [locationManager setDelegate:nil];
}
@end

There is however something that I have been attempting to do for a long time yet so far was unable.
You’ll notice that while the app is running on the iPhone, that if you move away from the current location by using a swipe gesture that the app will automatically move the view back such that the current location is centred once again (the initial region). I’ve been trying to disable this feature, so that when the app first starts up it is centred however after start up the user is free to pan around with swipes and remains without it moving back to the original start up view, however when an annotation is put down I do want the view to centre on that just added annotation.

I’'ve been playing around with this for over a week and started to get some strange results, and after a while I concluded I didn’t understand enough to accomplish that goal, I abandoned that code and started over. What you see above is what was asked for in the challenges. However after reviewing the solution in the archive (See 18. Whereami), I noticed that Joe makes use of a class called NSUserDefaults. His code seems to behave exactly the same as my code, and we haven’t seen this class yet in the book and wonder why its necessary.

Any help understanding this stuff is greatly appreciated.


#2

That was pretty inventive to look up the Subtitle property. I took a hint from you on that :wink:

I decided to store the date as a raw NSDate object, and then format it when it was called for. Then you could re-edit the date display while retaining use of a raw NSDate item for something else later, instead of turning it into a string when the map point is created.

BNRMapPoint.m

[code]#import “BNRMapPoint.h”

@implementation BNRMapPoint

@synthesize coordinate, title, creationDate, subtitle;

-(id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
self = [super init];

coordinate = c;
[self setTitle:t];
creationDate = [NSDate date];

return self;

}

-(id)init
{
return [self initWithCoordinate:CLLocationCoordinate2DMake(43.07, -89.32) title:@“Hometown”];
}

-(NSString *)subtitle
{
NSDateFormatter *dF = [[NSDateFormatter alloc] init];
[dF setDateStyle:NSDateFormatterShortStyle];
[dF setTimeStyle:NSDateFormatterLongStyle];
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@“en_US”];
[dF setLocale];
return [dF stringFromDate:creationDate];
}
@end
[/code]

I’ve been doing Cocoa on the desktop for a while. You have an “OK” solution to the map type, but it can be a lot simpler. I at first thought of case… but that’s too much code.

A segmented control has a index number of the selected button. :bulb:

And the map type is an enumeration…

[quote]MKMapType
The type of map to display.

enum {
MKMapTypeStandard,
MKMapTypeSatellite,
MKMapTypeHybrid
};
typedef NSUInteger MKMapType;[/quote]

each item in the enumeration can be called by it’s position in the array.
See where I am going…
Item 0 of the enum could be connected to segment 0 of the control. Item 1 of the enum to segment 1 of the control.

WhereAmIViewController.m

[code]//silver challenge

  • (IBAction)styleChanged:(id)sender
    {
    [worldView setMapType:[sender selectedSegmentIndex]];
    }
    [/code]

#3

Roggy,

The delegate function:

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
    CLLocationCoordinate2D loc = [userLocation coordinate];
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(loc, 250, 250);
    [worldView setRegion:region animated:YES];
    }
}

get’s called every time the phone thinks it moved, but ultimately it’s the [worldview setRegion:region animated:YES]; that is causing the centering and zooming after panning.

So you could wrap the above in an “IF” statement and use a bool that then gets set to false once you’ve successfully centered on the location you want. You’ll need to determine what event sets the bool back to true in the event you want it zoom again (user has travelled x meters, user drops an annotation, your choice!).

Hope this helps.


#4

Here’s my solution for the Gold. I could probably do with a space between the name of the city and the date, but at least it’s working!

This is all from WhereamiViewController.m - the rest of the code is unchanged.

- (void)foundLocation:(CLLocation *)loc
{
    CLLocationCoordinate2D coord = [loc coordinate];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    
    NSString *allText = [[locationTitleField text] stringByAppendingString:[dateFormatter stringFromDate:[NSDate date]]];

    // Create an instance of BNRMapPoint with the current data  
    BNRMapPoint *mp = [[BNRMapPoint alloc] initWithCoordinate:coord title:allText];
    
    // Add it to the map view
    [worldView addAnnotation:mp];

    ... etc