Silver Challenge Solution


#1

I solved the Silver Challenge by adding an IBAction connected with the segmented control in the XIB, code below:

[size=150].h file[/size]

[code]#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;

}

//received the target-action from segmented control to switch the map type

  • (IBAction)segControlClicked:(id)sender;

  • (void)findLocation;

  • (void)foundLocation:(CLLocation *)loc;

@end[/code]

[size=150]
.m file[/size]

[code]
#import “WhereAmIViewController.h”
#import “BNRMapPoint.h”

@interface WhereAmIViewController ()

@end

@implementation WhereAmIViewController

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

  • (id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {
    //create the location manager object
    locationManager = [[CLLocationManager alloc]init];

      [locationManager setDelegate:self];
      
      //and we want it to be as accurate as possible
      //regradless of how much time/power it takes
      [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    

    }
    return self;
    }

  • (IBAction)segControlClicked:(id)sender
    {
    int clickedSegment = [sender selectedSegmentIndex];
    switch (clickedSegment) {
    case 0:
    [worldView setMapType:MKMapTypeStandard];
    break;
    case 1:
    [worldView setMapType:MKMapTypeSatellite];
    break;
    case 2:
    [worldView setMapType:MKMapTypeHybrid];
    break;
    }

}

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

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

    //create an instace of BNRMapPoint with the current data
    BNRMapPoint *mp = [[BNRMapPoint alloc] initWithCoordiante:coord title:[locationTitleField text]];

    //add it to the map view
    [worldView addAnnotation:mp];

    //zoom the region to this annotation
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coord, 250, 250);
    [worldView setRegion:region animated:YES];

    //reset the UI
    [locationTitleField setText:@""];
    [activityIndicator stopAnimating];
    [locationTitleField setHidden:NO];
    [locationManager stopUpdatingLocation];

}

//UITextField delegate method

  • (BOOL)textFieldShouldReturn:(UITextField *)textField
    {
    [self findLocation];
    //tell the textField to give up its first responder status to make the keyboard disappear
    [textField resignFirstResponder];
    return YES;
    }

//sends the delegate a message once the user location was established

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

  • (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];

    //CCLocationManager will return the last found location of the
    //device first, you don’t 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)dealloc
    {
    //tell the location manager to stop sending us messages
    [locationManager setDelegate:nil];
    }

@end[/code]

Another way of doing it is to register a target-action method using the UIControlEventValueChanged as show below

[segmentedControl addTarget:self action:@selector(userChangedMapType:) forControlEvents:UIControlEventValueChanged];
and add implementing the - (void)userChangedMapType to adjust the map type accordingly.

Does anyone know what is the prefer method of solving this challenge and what are the advantages / disadvantages of doing it via IBAction vs UIControlEventValueChanged?


#2

If figured for something as simple as this, just wiring up an action would suffice.

However, after wiring up the IBOutlet that I created, and then pointing the segmented control’s Touch Up Inside to my IBAction, the action is not firing.

I am not sure what I have missed.


#3

[quote=“bouche”]However, after wiring up the IBOutlet that I created, and then pointing the segmented control’s Touch Up Inside to my IBAction, the action is not firing.[/quote]Try linking it to Value Changed under Sent Items of the UISegmentedControl you made in your .xib and see if that helps.

In response to the OP, in experimenting I found that if the UISegmentedControl is created in the .xib, you have to make an IBAction method to receive the Value Changed event. If it is created programmatically, you have to register with it and provide a selector for it to work… [segmentedControl addTarget:self action:@selector(action:) forControlEvents:UIControlEventValueChanged];


#4

thanks! that did it. I didn’t even see that action before. I just went for the classic ‘touchUp Inside’


#5

No worries, glad I could help. I actually found out what it used because I find it’s easier sometimes to control drag from the item in interface builder into the source ViewController file by using the assistant editor in xcode. Doing that seems to hook things up the perfect way, and automatically fills out some method names for me so I don’t forget.


#6

Actually, even if you create your segmented selector in the .xib, you can still use:

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

It goes in the viewDidLoad portion of your WhereamiViewController.m and you’ll need to create a function named changeMapType to handle the changes, just as you would have to have a function to receive an IBAction.


#7

I have some issues to use the approach by registering the target.

I have the following coding in WhereamiViewController.h:

{
...
    //Silver Challenge
    IBOutlet UISegmentedControl *mapTypeControl;
}
...
- (void)userChangedMapType;

and in WhereamiViewController.m

- (void)viewDidLoad
{
    [worldView setShowsUserLocation:YES];
    
    //UISegmentedControl *segmentedControl;
    [mapTypeControl addTarget:self
                       action:@selector(userChangedMapType:)
             forControlEvents:UIControlEventValueChanged];
}

and

- (void)userChangedMapType
{
    switch (mapTypeControl.selectedSegmentIndex)
    {
        case 0:
        {
            //worldView.mapType = MKMapTypeSatellite;
            [worldView setMapType:MKMapTypeSatellite];
            break;
        }
        case 1:
        {
            worldView.mapType= MKMapTypeStandard;
            break;
        }
        default:
        {
            worldView.mapType = MKMapTypeHybrid;
            break;
        }
    }
}

When I run this I get an ugly exception: [WhereamiViewController userChangedMapType:]: unrecognized selector sent to instance 0x7b5c060
In the xib fiel I only have the Outlet to the Segmented Control created for this.

Any idea what is wrong…?


#8

action:@selector([color=#FF0000]userChangedMapType:[/color])
- (void)[color=#FF0000]userChangedMapType[/color] {…}

You are registering an action for a method that takes a single argument, but the actual method provided takes no argument.

Could this be the problem?


#9

Thank you! That’s it.
I thought the “:” is required there.