My Map Tab Challenge Implementation <<SPOILER WARNING>>


#1

<>

I succeeded this afternoon in implementing the map tab challenge. It borrows heavily from WhereAmI (or WhereWasI) but I decided to implement it independently of WhereAmI.

So, if you’re stuck, here’s how I did it. If you want to try to figure it out on your own, then READ NO FURTHER!

You’ll notice I added copious comments to code extracted from WhereAmI, which is mainly for my own notes and understanding of what is going on.

Also: It is possible that I have missed some memory management stuff here, or failed to handle memory warnings. Perhaps someone could let me know if I need to add that in…

I also created a basic icon, called “mapicon.png”, but you can always just use one of the other two icons that are provided on this website as a placeholder.

[code]//
// MapViewController.h
// HypnoTime
//
// Created by Steven Britton on 10-09-08.
// Copyright 2010 MyCompanyName. All rights reserved.
//

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

@interface MapViewController : UIViewController {

// CLLocationManager *locationManager; // Pointer to an instance of a CLLocationManager object, so we can find the user’s location
IBOutlet MKMapView *mapView;
}

@end
[/code]

[code]//
// MapViewController.m
// HypnoTime
//
// Created by Steven Britton on 10-09-08.
// Copyright 2010 MyCompanyName. All rights reserved.
//

#import “MapViewController.h”

@implementation MapViewController

-(id)init
{
// Call the superClass’ designated initializer
[super initWithNibName:@"MapViewController"
bundle:nil];

// Get the tab bar item
UITabBarItem *tbi = [self tabBarItem];

// Give it a label
[tbi setTitle:@"Map"];

UIImage *i = [UIImage imageNamed:@"mapicon.png"];
[tbi setImage:i];

return self;

}

  • (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
    {
    // Disregard parameters - nib name is an implementation detail
    return [self init];
    }

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

  • (void)viewDidLoad {
    // Set the delegate (self)
    [mapView setDelegate:self];
    [mapView setMapType:MKMapTypeHybrid];
    [mapView setShowsUserLocation:YES];
    [super viewDidLoad];
    }

  • (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
    {
    // mv is a pointer to an instance of MKMapView, which is the map we’re displaying on the
    // device’s screen.

    // views is an array consisting of pointers to instance(s) of MKAnnotationView objects.
    // We only want one of them - the first:

    MKAnnotationView *annotationView = [views objectAtIndex:0]; // Extract first pointer from the array.

    // the “id” in the code below is part of the declaration for mp. It means “any Object”
    // the angled brackets declare that it must adhere to the protocol within them, in this
    // case, MKAnnotation. Since we’re sending an annotation object to it, it must, by design
    // adhere to the protocol, which means that messages you send to it (like “coordinate”, below)
    // will be handled properly.

    id mp = [annotationView annotation]; // mp is an instance of the annotation extracted
    // from annotationView (by sending the “annotation”
    // message to the object that annotationView points
    // to.

    // Now, create a local instance of MKCoordinateRegion structure, centered around the
    // coordinates found in mp (extracted using the “coordinate” message, which is within the
    // MKAnnotation protocol as declared in the line above, and 250m by 250m in each direction.

    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate], 250, 250);

    // Finally, we want to adjust the map to display the new region as we figured out here.
    // the “animated:YES” message tells the map to ZOOM in to the region, rather than just
    // display it. These animations are what make the user interface snazzy.

    [mv setRegion:region animated:YES];
    }

  • (void)didReceiveMemoryWarning {
    // Releases the view if it doesn’t have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren’t in use.
    }

  • (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    }

  • (void)dealloc {
    [super dealloc];
    }

@end
[/code]

And finally (for code) make the following change to your application: method in HypnoTimeAppDelegate:

// Create two view controllers
UIViewController *vc1 = [[HypnosisViewController alloc] init];
UIViewController *vc2 = [[CurrentTimeViewController alloc] init];
[b]UIViewController *vc3 = [[MapViewController alloc] init];[/b]

// Make an array containing the two view controllers
[b]NSArray *viewControllers = [NSArray arrayWithObjects:vc1, vc2, vc3, nil];[/b]

[vc1 release];
[vc2 release];
[b][vc3 release];[/b]

#2

Addendum:

After re-reading the section of Chapter 7 dealing with the lifecycle of a view controller, I noticed I failed to clean up after myself, so I’ve made the following changes to MapViewController.m:

[code]- (void)viewDidUnload {
[mapView release];
mapView = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

  • (void)dealloc {
    [mapView release];
    [super dealloc];
    }
    [/code]

#3

I’m trying to solve the same challenge, but I seem to be doing something wrong. My code looks pretty much exactly like yours (I’ve even tried using your code), but I keep throwing an exception:

This GDB was configured as "x86_64-apple-darwin".Attaching to process 11401.
2011-04-18 09:25:20.929 HypnoTime[11401:40b] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MKMapView)'
*** Call stack at first throw:
(
	0   CoreFoundation                      0x00dc45a9 __exceptionPreprocess + 185
	1   libobjc.A.dylib                     0x00f18313 objc_exception_throw + 44
	2   CoreFoundation                      0x00d7cef8 +[NSException raise:format:arguments:] + 136
	3   CoreFoundation                      0x00d7ce6a +[NSException raise:format:] + 58
	4   Foundation                          0x00784b73 _decodeObjectBinary + 3119
	5   Foundation                          0x00783d91 _decodeObject + 224
	6   UIKit                               0x00211979 -[UIRuntimeConnection initWithCoder:] + 212
	7   Foundation                          0x00784c24 _decodeObjectBinary + 3296
	8   Foundation                          0x007859f5 -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] + 1354
	9   Foundation                          0x00786024 -[NSArray(NSArray) initWithCoder:] + 596
	10  Foundation                          0x00784c24 _decodeObjectBinary + 3296
	11  Foundation                          0x00783d91 _decodeObject + 224
	12  UIKit                               0x00210c36 -[UINib instantiateWithOwner:options:] + 804
	13  UIKit                               0x00212ab7 -[NSBundle(UINSBundleAdditions) loadNibNamed:owner:options:] + 168
	14  UIKit                               0x000c8628 -[UIViewController _loadViewFromNibNamed:bundle:] + 70
	15  UIKit                               0x000c6134 -[UIViewController loadView] + 120
	16  UIKit                               0x000c600e -[UIViewController view] + 56
	17  UIKit                               0x000d8f54 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 120
	18  UIKit                               0x000d7aaa -[UITabBarController transitionFromViewController:toViewController:] + 64
...

When I created my viewcontroller, I added an MKMapView object on the view and connected it the outlet I created in my .h file. Am I supposed to connect anything else on the interface builder side?

Thanks…


#4

You need to link the target to the MapKit framework.


#5

DOH! So simple (and stupid). Good lesson learned on that.

How did you know from the output that I sent to you that that was the problem?

(EDIT)

Oh - and thanks a ton Joe. Really appreciate you taking time to help us all. Love the book.


#6

This is kind of a tricky one to catch, and it’s fairly rare.

The exception thrown was that you “cannot decode object of class (MKMapView).” Which is only going to happen if the implementation for MKMapView doesn’t exist. The only way the implementation for MKMapView to be missing is if the object file in which its binary code lives in is not linked into your target. And of course, the only way to link that information is to add the MapKit framework.


#7

Makes perfect sense now. Thanks again.


#8

Why have you commented out CLLocationManager *locationManager; and why are you not needing a CLLocationManagerDelegate ?
I thought we needed these to get the location in the first place, then use the mapView to display it. Have i understood all of this incorrectly?


#9

@dillios an MKMapView is perfectly capable of getting the user’s location on its own, via the showsUserLocation property. In the MapKit portion of the exercise, we only use Core Location to get a suitable location for setting an MKAnnotation.


#10

Could someone please explain how does this code work? I’ve been looking at it for a good 15 minutes and cant’t figure out how is mapView getting the user’s location…
What does the - (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
method do, exactly?


#11

Great, thanks for adding this. I had most of it, but had missed a couple of lines.

Also, if anyone sees this error:

_MKCoordinateRegionMakeWithDistance", referenced from:
-[MapViewController mapView:didAddAnnotationViews:] in MapViewController.o
ld: symbol(s) not found for architecture i386
collect2: ld returned 1 exit status

then check out this post.
forums.bignerdranch.com/viewtopic.php?f=47&t=356

Took me a little searching to re-remember how to link in the mapkit module. :open_mouth: Hopefully you’re not as daft as me for forgetting.


#12

I have been working on this for 6 hours and couldn’t figure it out.
This post just saved my life.


#13

How do you add zooming and the ability to add your location into the text field?
I can get the map showing but can’t implement any methods needed.


#14

Hi,

I did exactly as you’ve written and everything is building except my [mp setRegion:region]; statement. It somehow doesn’t recognize this function. I have the #import <MapKit/MapKit.h> so I’m not sure what is the problem.
Any suggestions what I may be missing.
Thanks.

artivilla99