All 3 challenges - with helpful comments


#1

Hey all,

Here is my take on the 3 challenges, it seems to work. I tried to comment as best as i could (there might be a few spots i missed or that includes comments to myself, just ignore those) :slight_smile:

WhereamiViewcontroller.h

[code]@interface WhereamiViewController : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate, UITextFieldDelegate>
{
CLLocationManager *locationManager;

IBOutlet MKMapView *worldView;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UITextField *locationTitleField;

// SILVER CHALLENGE
// Nothing special, like the above it is just an outlet.
IBOutlet UISegmentedControl *switchMapType;

}

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

// SILVER CHALLENGE
// Gets called by segmented control
-(void) changedMapType;[/code]

.m

[code]// Silver challenge
// Called every time the segmented control changes value
-(void) changedMapType {
// Called when the value changes on segmented control, since it was added as a target.
// Check what value the segmented control is on, and change map type.
if ([switchMapType selectedSegmentIndex] == 0)
[worldView setMapType:MKMapTypeStandard];

else if ([switchMapType selectedSegmentIndex] == 1)
    [worldView setMapType:MKMapTypeSatellite];

else if ([switchMapType selectedSegmentIndex] == 2)
    [worldView setMapType:MKMapTypeHybrid];

else
    NSLog(@"ERROR: Selecting world view");

}

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

// BRONZE CHALLENGE
// MKMapView has a method to set the map type, and it can take 3 different type of MKMapTypes. Read documentation for info on the types.
[worldView setMapType:MKMapTypeSatellite];

// Sets a target for the segmented control - It means that every time the value changes (as specified as UIControlEventValueChanged), it will call the selector (changedMapType).
[switchMapType addTarget:self
                  action:@selector(changedMapType)
        forControlEvents:UIControlEventValueChanged];

}[/code]

BNRMapPoint.h

// GOLD CHALLENGE: // Use to display date - Check documentation - like the required title property, the MKAnnotation has another property called subtitle - it will use such a property, if you made it (it is optional). @property (nonatomic, readonly, copy) NSString *subtitle;

.m

[code]-(id) initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
self = [super init];
if (self) {
coordinate = c;
[self setTitle:t]; // Use method to copy it, not assign.

    // Creates new date with current date
    NSDate *theDate = [NSDate date];
    
    // GOLD CHALLENGE:
    // NSDateFormatter class, have a method below that takes an NSDate object, converts it into a more read-able manner, and you specify how much information you want it to display using dateStyle and timeStyle - read the documentation on that method for info on which types to select. It returns a string, and can therefore be added directly to the instance variable, that the MKAnnotation will use as it's subtitle string.
    
    subtitle = [NSDateFormatter localizedStringFromDate:theDate
                                              dateStyle:NSDateFormatterMediumStyle
                                              timeStyle:NSDateFormatterMediumStyle];
}

return self;

}[/code]

Let me know what you think :slight_smile: Hope it helps someone.


#2

Wow thanks.
I was quite intimidated by doing the challenges but your code really made it worth while to check the forums.


#3

I also used the subtitle property to put the date into the Annotation, with the slight modification to the init method. I chose to set the TimeStyle to NSDateFormatterNoStyle so that I don’t show the time in the annotation.

Here’s my version of the initWithCoordinate: method.

[code]-(id) initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t {

// Gold challenge, getting the Date into the location... 
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
// I don't want to show ANY time at all
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateFormatter setDateStyle:NSDateFormatterLongStyle];
NSString * dateString = [dateFormatter stringFromDate:[NSDate date]];

self = [super init];
if (self) {
    coordinate = c;
    [self setTitle:t];
    // Gold challenge, set the subtitle to the "dateString" set just before
    [self setSubtitle:dateString];
}
return self;

}[/code]
And yes, to do this you need to declare the other property, the subtitle, in your BNRMapPoint.h file, just the same as you earlier had declared the title property:


#4

Hi

On the topic of the Silver challenge, the UISegmentedControl can also be wired in interface builder to send a IBAction message to WhereamiViewController by Ctrl dragging from the UISegmentedControl to File’s Owner , although the documentation states that :

You register the target-action methods for a segmented control using the UIControlEventValueChanged constant as shown below.

[segmentedControl addTarget:self
action:@selector(action:)
forControlEvents:UIControlEventValueChanged];

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 *mapTypeSegmentedControl;

}

- (void)findLocation;
- (void)foundLocation:(CLLocation *)loc;
- (IBAction)mapTypeSegmentedControlValueChanged:(id)sender;


@end

Then in WhereamiViewController.m

-- (void)viewDidLoad
{
    [worldView setShowsUserLocation:YES];
    
    
//    Definition of MKMapType in MKTypes.h
//    enum {
//        MKMapTypeStandard = 0,
//        MKMapTypeSatellite,
//        MKMapTypeHybrid
//    };
//    typedef NSUInteger MKMapType;

    
//  We know it is MKMapTypeSatellite, but better be proactive    
    MKMapType mapType = [worldView mapType];
    
//  It works as the type of SegmentIndex is NSInteger and Segments are zero based also, 
//  that is the first segment has index 0, the second index 1 and so on 
    
//  This trick works, only if you create the segments of the SegmentControl in interface builder
//  in the same (zero based) order of the MKMapType above, that is
    
//    Segment 0 - Standard
//    Segment 1 - Satellite
//    Segment 2 - Hybrid
    
//  As the enum is a plain C construct, it does not provide (I think) a way to create the segments dynamically in runtime 
    
    [mapTypeSegmentedControl setSelectedSegmentIndex:mapType];
}

when the selected segment changes the mapType of the worldView can be set in the same way

- (IBAction)mapTypeSegmentedControlValueChanged:(id)sender;
{
    MKMapType mapType = [mapTypeSegmentedControl selectedSegmentIndex];
    [worldView setMapType:mapType];
}