2011/12

 
 

Introduction

The NearestMe Laps (Lab 8 and Lab 9) explore the use of data persistence, and the notion of location-aware computing.  It utilises the skills developed from previous labs; in particular it relies on a working knowledge of the creation and manipulation of table views.  In Lab 8, you will focus on developing an initial app that exploits two views within a UITabBarController, and then explore how to use Core Location to determine the current position, and view this graphically using MapKit.  You will also be walked through the process of creating a modal view designed to get data from the user, and using textviews.  Lab 9 will then explore how the resulting data can be stored using pLists and Core Data, as well as presenting one way to handle proximity with respect to geo-tagged data and the user’s current position.  Both Labs work together - Lab 9 starts where Lab 8 ends, and thus they should be done together.


Todays Tasks:

Walkthrough - Step 1: Create a new App and manage two view controllers.


The project you will be developing involves the use of a tab bar to manage the views, and will store data within core data.  As you will need a project template that includes the code to support Core Data, and as we will be looking at using a UITabBarController programmatically, begin by creating an Empty Application application called NearesttMe.  Type in the name into the Product Name field and the Class Prefix.  Ensure that the device Family is set to iPhone, and that you are not using Automatic Reference Counting or Unit Testing.  Tick the “Use Core Data” box, to ensure that we have the necessary stub code included within our app delegate.  We will look at this code later


Start by creating two new controllers - these will be used within our tab bar controller.  The first should be called “MyLocListTableViewController” and should be a subclass of a UITableViewController (with no nib file).  The second should be called “NearestLocationViewController”, and should be a subclass of a UIViewController with a nib file (for iPhone).  Open up the nib, and insert a UILabel with the text “Nearest Location” - this is just a placeholder for now to identify the currently active view controller.


Each view controller managed by a UITableView controller defines its own title and image to appear in the Tab Bar.  These should be defined when the view controllers are initialised (not loaded), so add the following lines to the initialisation methods of both view controllers:


In the NearestLocationViewController.m initWithNibName:bundle: method:


    [self setTabBarItem:[[UITabBarItem alloc] initWithTitle:@"Nearest" image:[UIImage imageNamed:@"103-map.png"] tag:0]];


In the MyLocListTableViewController.m initWithStyle: method:


    [self setTabBarItem:[[UITabBarItem alloc] initWithTitle:@"Locations" image:[UIImage imageNamed:@"74-location.png"] tag:1]];


These tab bar items rely on the two *.png files - unlike normal image files, these are “alpha” channel files, i.e. the image is based on differing levels of transparency, rather than colour.  The following images from the royalty free Glyphish set of mobile app icons have been use for the tab bar items:


  1. Location Image (74-location.png)

  2. Map Image (103-map.png)


Note - it is possible to group files together within the file list to organise them.  This does not move the files within the directory structure, or move them around the file system, but it can aid development, by putting all the files and resources associated to a given task or View Controller into a single group.  For example, in the image opposite, the files associated with the NearestLocationViewController have been grouped together in a group called “Nearest Location VC Group”. To create a group, select the files that should belong to the group, and right-click the mouse.  From the menu, select the option “New Group from Selection”, and it will create a group, which you can then add files to, or rename.


As both view controllers will make use of Core Data, we will want to pass into each view controller an object relating to a Managed Object Context.  If you look at the App Delegate, you will notice that a number of additional instance variables and properties have been created:


@synthesize window = _window;

@synthesize managedObjectContext = __managedObjectContext;

@synthesize managedObjectModel = __managedObjectModel;

@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;


Further in the code, you will also find methods corresponding to each of the properties, which are responsible for creating the necessary objects for Core Data.  Fortunately, in most cases, we will not have to worry about this code.  However, when using Core Data objects, we will need the managed object context.


Return to the header files for each of the two view controllers you have defined (i.e. NearestLocationViewController.h and MyLocListTableViewController.h) and add the following ivar to both:


    NSManagedObjectContext *managedObjectContext;


Specify their properties (nonatomic, retain), and synthesise them in their respective source files.


Now we have our two initial view controllers, we want to add them to a UITableViewController within the App delegate.  Within the application:didFinishLaunchingWithOptions: method, start by obtaining the managed Object Context, which will be passed through to each view controller:


    // ==============================================================================================

    // Obtain the managed object context.


    NSManagedObjectContext *context = [self managedObjectContext];

    if (!context) {

        NSLog(@"No NSManagedObjectContext generated");

    }


The next task involves creating the view controller for the table view.  Note that we will also be creating a UINavigationController which contains our table view; this will give us a Navigation Bar which can be used for the title and for edit buttons, etc.  Therefore:


    // ==============================================================================================

    // Create the views to appear in the tab bar

    //

    // View 1 - the location list and editor

    //

    MyLocListTableViewController *rootViewController = [[MyLocListTableViewController alloc]

                                                                initWithStyle:UITableViewStylePlain];

   

    // Pass the managed object context to the view controller.

    [rootViewController setManagedObjectContext:context];

   

    UINavigationController *aNavigationController = [[UINavigationController alloc]

                                                     initWithRootViewController:rootViewController];

   

Secondly, create an instance of the NearestLocationViewController :


    //

    // View 2 - the nearest item to me view

    //

   

    NearestLocationViewController *nearestLocVC = [[NearestLocationViewController alloc]

                                                            initWithNibName:@"NearestLocationViewController" bundle:nil];


    // Pass the managed object context to the view controller.

    [nearestLocVC setManagedObjectContext:context];


Finally, we want to package them within an array, and create a UITabBarController.  Modify the app delegate to include an ivar for the UITabBarController, and then assign to this the new UITabBarController .


    //

    // Tab Bar Controller

    //

    tabBarController = [[UITabBarController alloc] init];

    [tabBarController setViewControllers:[NSArray arrayWithObjects:aNavigationController, nearestLocVC, nil]];


Finish off by adding the tabBarController’s view to the window’s view, and releasing the other view controllers (i.e. the table and navigation view controllers, and the nearestLocationViewController), as these have all been retained by the tabBarController or its elements.


    [[self window] addSubview:[tabBarController view]];

   

    [rootViewController release];

    [aNavigationController release];

    [nearestLocVC release];

    // ==============================================================================================


Import the header files of the two view controllers in the implementation file for the app delegate, ad then compile and test the code.


A note on changing models in Core Data


When using core data, your application will construct and connect to a persistent data base in the application's sandboxed directory structure.  Once created, the application assumes that the structure of the data base will not change.  If it does, you will need to “remove the application” from the device or simulator, so that a new database can be built.  If you don’t, the code will abort within the app delegate’s persistentStoreCoordinator method, and you will get an error which includes the message: “reason = "The model used to open the store is incompatible with the one used to create the store";

”. This can happen during development, as models are changed, so watch out for this.  The easiest way of resolving this in the simulator is to reset the simulator’s content and settings.




Walkthrough - Step 2: Set up Core Location, and obtain the current location.


The two frameworks: Core Location and MapKit provide support for obtaining location-based information for the device, and displaying maps and annotations graphically, as well as support for geocoding location data (i.e. converting lat/long coordinates to street-level addresses, and vice versa).  However, to use these frameworks, they will need to be included in your project.


Start by linking these to your project.  In Xcode, select the name of your project in the file list, to display the project summary.  Then, in the main window, select “Build Phases”, and open the section on “Link Binary With Libraries”.  You will notice that there are already several frameworks linked to your project, including UIKit and Foundation (which are included in every project), and CoreData, which has been included as you selected the inclusion of Core Data support.  Under these - you will see a “+” button, which can be used to add further frameworks.  Open this window and add: “CoreLocation.framework”, and “MapKit.framework”.




We can now make use of the API for each framework within our project.  The next phase involves setting up Core Location to update the current location of the device on a map in the view controller, to check that this is working.  We’ll start by creating a location manager object, and configure its desired accuracy and distanceFilter properties.  We will then create the delegate methods locationManager:didUpdateToLocation:fromLocation: and locationManager:didFailWithError:, which will be called if there is a change to the users location, or problems with obtaining the current location respectively.  These will then be used to display the current latitude and longitude on a map.


Start by importing <CoreLocation/CoreLocation.h> in your NearestLocationViewController.h, and include the the ivar/property for a location manager:


    CLLocationManager *locationManager;


Remembering to declare the property (nonatomic, retain) and synthesise the property in the source file.


Create the getter method locationManager to obtain the current location manager object, initialising it if necessary.  Start by checking to see if the object exists - if so, then return the object:


// ================================================================================================

#pragma mark - Core Location Methods


- (CLLocationManager *)locationManager {

    if (locationManager != nil) {

        return locationManager;

    }

   

If the object doesn’t exist, then we need to create and configure it.  There are three main properties that will need defining:

  1. The desired accuracy - the accuracy of the location data.  This can also affect the time required, and power requirements of the location-aware hardware in the device; low accuracy settings typically require less power (and can often be very efficient, if only significant location change notification changes are required).

  2. The distance filter - This corresponds to the minimum distance (measured in meters) a device must move horizontally before changes to the location are sent to the delegate

  3. The delegate - the object to which changes to the location are sent


In this case, we want relatively high accuracy, with a distance filter of 25 meters, and use “self” as the delegate.


    locationManager = [[CLLocationManager alloc] init];

    [locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters];

    [locationManager setDistanceFilter:10];

    [locationManager setDelegate:self];

   

    return locationManager;

}


As the view controller will act as the location manager’s delegate, we need to adhere to the location protocol CLLocationManagerDelegate, and implement the protocol methods.  Update the interface definition in the header file to use the CLLocationManagerDelegate protocol, and implement the following two methods:


// ================================================================================================

#pragma mark - Core Location Delegate Methods


- (void) locationManager:(CLLocationManager *)manager

     didUpdateToLocation:(CLLocation *)newLocation

            fromLocation:(CLLocation *)oldLocation {

   

    NSLog(@"NearestLocationViewController new location: latitude %+.6f, longitude %+.6f\n",

          [newLocation coordinate].latitude,

          [newLocation coordinate].longitude);   

}


The above method is called when there are new location events; i.e. the user has moved at least distanceFilter meters from the previous position.  For now, we display the coordinates in the log.  Note that the object returned from calling coordinate on the newLocation object returns aCLLocationCoordinate2D, which is defined as a C struct, and not an Objective-C object.  Hence we access the elements using the C dot notation.


- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {

    // For now, do nothing other than report to the log

    NSLog(@"Unable to get location events");

}


This method is called if there are problems with obtaining device’s current location.  Although optional, it can be used to determine why there were problems (such as the user disabling location services, or temporarily not being able to access the location).  For now we will do nothing, other than report the case to the log.  Later, we will use this to disable any location activities (such as disabling a button the user could use to record the current location).


All that is left now, is to create a location manager object, and to either enable (or disable) location events, depending on whether or not we are looking at the view.  Because of how we implemented the getter accessor method for locationManager, the location manager will be created automatically when this method is first called.  Location services can be started by calling the method startUpdatingLocation, and stopped using stopUpdatingLocation.  Once started, updates to the location will be generated whether or not the view controller’s view is present.  For a simple application that always uses location awareness, we might simply start the service within the viewDidLoad method.  However, in this case, we are using the location service to display the current location when the view controller is active (i.e. selected using the Tab Bar Controller).  Therefore, we will start the service when the view is about to appear, and stop it when the view is about to disappear:


- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    NSLog(@"NearestLocationViewController about to appear");

    [[self locationManager] startUpdatingLocation];

}


- (void)viewWillDisappear:(BOOL)animated{

    [super viewWillDisappear:animated];

    NSLog(@"NearestLocationViewController about to disappear");

    [[self locationManager] stopUpdatingLocation];

}


We’ve included log messages to verify that these methods have been called.  These can be deleted at a later stage.


Compile and Run.  Note that nothing much will happen until you select the “Nearest” tab on your application, at which point the simulator will ask whether the application should use the current location.  Select OK.  After this, log messages will be generated (see below).




You may notice that the location changes over time.  The iOS Simulator in Xcode 4.2 includes the ability to simulate location changes, as a “virtual user” travels around a given area (typically Cupertino, the home of Apple’s main campus).  The routes (and hence speed, frequency, area etc) can be changed by modifying the iOS simulator settings under the Debug menu bar:




In the remainder of this lab, we will assume that “City Bike Ride” has been chosen.


Finally, modify the NearestLocationViewController.xib nib file so that the latitude and longitude can be displayed within two UILabels.  Update the class definition accordingly, and check that the labels reflect the current location. The next step is to display this location on a Map.


Walkthrough - Step 3: Set up MapKit, and graphically present the current location.


In this step we will add a map to the NearestLocationViewController nib, and display an area around the current position, which will update whenever a new location event is received.  Add the new ivar mapView to the view controller’s header file, make it an IBOutlet property, and synthesise it in the source file.  You will also need to #import the MapKit header file <MapKit/MapKit.h> in this header file as well.
Open the nib file, drag a map view onto the main view, and connect this to the mapView property.  Ensure that you are simulating a tab bar as the bottom bar, as this will affect the positioning of the map. Within the Attributes Inspector of the Interface Builder, if the map view is selected, you will see a tick box for “Shows User Location”.  Tick this box, to enable a “pulsing blue bin to indicate the current location”.  We are now ready to update the code to show the relevant region on the map.


As map views show a region of a map in a rectangular area, we need to define a region for the map to zoom to, given some coordinate and scale.  We can do this using the method MKCoordinateRegionMakeWithDistance, which creates a new region given the location of the central point, the north-south distance (measured in meters) and the east-west distance (measured in meters).  This region is then used to create a second region based on the dimensions of the map view in the nib file.  The method regionThatFits: adjusts the aspect ratio of the specified region to ensure that it will fit into the map view.  Now we have a finalised region, we can tell the map view to display the specified region.


Add the following code to the locationManager:didUpdateToLocation:fromLocation: method, after having set the latitude and longitude values within the labels.


    MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance([newLocation coordinate],

                        0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);

    MKCoordinateRegion adjustedRegion = [mapView regionThatFits:viewRegion];               

    [mapView setRegion:adjustedRegion animated:YES];


Note that we have the symbol METERS_PER_MILE in the code, which will cause the editor to complain, and will cause the compiler to fail.  This symbol represents the number of meters per mile, so that we can define a region in terms of miles.  In the above code, we started by generating a half mile by half mile region, before fitting this to the view.


#define METERS_PER_MILE 1609.344


Add the following line to the NearestLocationViewController.h header file, compile the code and test.


So far we have demonstrated how to show the user’s current position on a map, and use that same position to determine a geographical region on the map.  The next stage is to demonstrate how to place annotations on a map, and create a modal view which will be used to request data from the user prior to creating entries in Core Data.


Walkthrough - Step 4: Create a modal View to get data from the user, and position an annotated point on the map.


In this step, you will develop a new view controller that will be presented modally whenever a new location is to be added to Core Data.  We will implement an add button and insert it into the Navigation Menu of the MyLocListTableViewController location view controller; this in turn will present the new view controller, which will be used to acquire data (in addition to the current location).  When the view is dismissed, we will retrieve the data prior to saving in Core Data.




Create a subclass of the UIViewController class with a nib file, called NewLocationEntryViewController.  We are going to start by creating a nib that is similar to that used NearestLocationViewController, but with some additional fields.  Therefore, add the iVar properties, synthesise, and add the UI elements to the nib file so that it is similar to the image above.  Remember to select the “Shows User Location” property of the MKMapView as before.


    CLLocation *location;

   

    UILabel *latitudeLabel;

    UILabel *longitudeLabel;

    MKMapView *mapView;

    UITextField *nameTextField;

    UITextField *commentTextField;

    UINavigationBar *navigationBar;


We are going to be using UITextFields to gather data from the user.  These UI Elements will require a delegate to manage the keyboard; whilst we will discuss this later, ensure that the NewLocationEntryViewController class adhere’s to the UITextFieldDelegate protocol (see the header fragment given below).  We also will be passing in a location into this view controller, which will be presented on a map, and the user will be asked for additional information about the location.  Therefore we need to ensure that the location ivar location is set as a property, and is synthesised.


Note that in this nib, we have included a Navigation Bar, which contains two Bar Button Items - Cancel and Done.  In previous labs, when we have created a UITableViewController, we would add this to a UINavigationController to not only allow navigation, but also to provide a way of adding Bar Button items to the menu bar.  Import both the Core Location and Mapkit header files into the NewLocationEntryViewController.h header file, include the METERS_PER_MILE macro, and ensure that the interface elements are connected to the properties.


As this new view will act as a modal view, we also need to define a protocol to determine what methods should be implemented by the parent view controller.  In this case, we simply want to have a “dismiss” method.  The convention and best practice approach when defining delegate protocols for a class is simply to use the name “classnamedelegate”, so add the following protocol definition after the end of the @interface...@end block in the NewLocationEntryViewController.h header file:


@protocol NewLocationEntryViewControllerDelegate

- (void) newLocationEntryComplete:(NewLocationEntryViewController *)controller wasCancelled:(BOOL)cancelled;

@end


Note that this protocol block defines a single method, newLocationEntryComplete:wasCancelled:, which will be called when the modal (child) view controller (NewLocationEntryViewController) asks the parent view controller (MyLocListTableViewController) to be dismissed.  As the aim of the modal view is to elicit information about a location and give the user the option of saving the data or cancelling, we also need a boolean flag (cancelled) to say whether or not we want to keep the data. Finally, we pass as an argument a reference to the child view controller (controller) as a convenience.


Now we have the protocol, we need a delegate property to store the delegate (which will be set to the parent view controller by the parent), and we need to state that this view controller will adhere to the new protocol.  As all of this will appear before the definition of the protocol, we need a forward class definition.  The first part of the header file is given below, to illustrate what is needed.


//  NewLocationEntryViewController.h


#import <UIKit/UIKit.h>

#import <MapKit/MapKit.h>

#import <CoreLocation/CoreLocation.h>


#define METERS_PER_MILE 1609.344


@protocol NewLocationEntryViewControllerDelegate;


@interface NewLocationEntryViewController : UIViewController <UITextFieldDelegate> {

    id <NewLocationEntryViewControllerDelegate> delegate;


    CLLocation *location;

   

    ...

}


@property (nonatomic, retain) id <NewLocationEntryViewControllerDelegate> delegate;

@property (nonatomic, retain) CLLocation *location;

...


Before we can test this class, we will need a way of allowing the user to dismiss the modal view controller, either by cancelling the view controller, or by saving the results.  Create the following two IBAction methods within the header file, and implement the corresponding methods within the corresponding  implementation file.  In both cases, call the newLocationEntryComplete:wasCancelled: method on the delegate, passing in self as the controller, and either YES or NO, depending on whether the done or cancel buttons were pressed.  Ensure that these actions are linked to the two Bar Button Items - Cancel and Done in the nib file.


- (IBAction)done:(id)sender {

    [[self delegate] newLocationEntryComplete:self wasCancelled:NO];

}


- (IBAction)cancel:(id)sender {

    [[self delegate] newLocationEntryComplete:self wasCancelled:YES];

}


Finally, we need a way of getting the parent to present the modal view controller, and dismissing it afterwards.  Open the parent class (MyLocListTableViewController) header file, import the NewLocationEntryViewController’s header file, add the NewLocationEntryViewControllerDelegate protocol to the class interface, and add the protocol method definition (newLocationEntryComplete:wasCancelled:) to the header file.  Also add an addLocation method definition as this will be the method used to present the modal view. 


//  MyLocListTableViewController.h


#import <UIKit/UIKit.h>

#import "NewLocationEntryViewController.h"


@interface MyLocListTableViewController : UITableViewController <NewLocationEntryViewControllerDelegate> {

    NSManagedObjectContext *managedObjectContext;

}


@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;


- (void) addLocation;

- (void) newLocationEntryComplete:(NewLocationEntryViewController *)controller wasCancelled:(BOOL)cancelled;


@end


We can now add the final pieces of code to the MyLocListTableViewController implementation, so that we can test that the delegate is working.  Modify the viewDidLoad method to set the title to the string @"NearestMe" and add a UIBarButtonItem of type UIBarButtonSystemItemAdd with the action @selector(addLocation) on the target self.  Set this as the right bar button item.  Then, add the following delegate method:


// ================================================================================================

#pragma mark - Delegate Methods


- (void) newLocationEntryComplete:(NewLocationEntryViewController *)controller wasCancelled:(BOOL)cancelled {

   

    NSLog(@"The %@ button was pressed", (cancelled==NO?@"Done":@"Cancle"));       

    [self dismissModalViewControllerAnimated:YES];

}

// ================================================================================================


Finally, we can define the addLocation method, to create a new instance of the NewLocationEntryViewController class.  Set the transition style to setModalTransitionStyle:UIModalTransitionStyleCoverVertical, set the delegate to self, and present the controller.  Note that in the code fragment (below), we set the view controller to be autoreleased - this is an optional approach to releasing it at at the end of the method.


- (void) addLocation {

   

    NewLocationEntryViewController *myVC = [[[NewLocationEntryViewController alloc]

                        initWithNibName:@"NewLocationEntryViewController" bundle:nil] autorelease];

    [myVC setModalTransitionStyle:UIModalTransitionStyleCoverVertical];

    [myVC setDelegate:self];


    // Create a fake location for testing purposes

    CLLocation *testLoc = [[[CLLocation alloc] initWithLatitude:37.330174 longitude:-122.032774] autorelease];

    [myVC setLocation:testLoc];

       

    [self presentModalViewController:myVC animated:YES];   

}

// ================================================================================================


Compile the code, and check that it works.  You should now see the “+” button appear on the Navigation bar of the NearestMe tab.  Clicking this button should present (modally) the child view, which can be cancelled or closed (using “Done”).  The map should also appear (although not scaled), and whilst we will have the labels and text fields on the view, they are not yet being used.  However, if you look closely at the definition of the addLocation method (above) you will notice that we created a temporary CLLocation object to represent a location, and that this was passed to the modal view controller.  We now want to do something with this.


Modify the viewDidLoad method to do the following:

  1. 1)Set the title in the UINavigation bar using the following line of code:


    [[[self navigationBar] topItem] setTitle:@"Add Location"];


  1. 2)Set the latitude and longitude labels to display the corresponding values stored within the location property

  2. 3)Scale the map accordingly to show the area around the location property.


If you are unclear to do this - take a look at the code you included in the locationManager:didUpdateToLocation:fromLocation: method of your NearestLocationViewController class.


If you test and run, you will now notice that the street map of Cupertino now appears, and the current location may well appear, but we still don’t know the actual location of the location coordinate.  For that, we need to create an annotation object that conforms to the MKAnnotation protocol.


Annotations are objects that appear graphically on a map, and can be used to provide basic information about that annotation.  Each annotation that appears is an instance of some object that adheres to  MKAnnotation. Therefore, it must implement a coordinate property, but can also implement properties for title and subtitle.  In this example, we will create a subclass of an NSObject, and implement these properties.


Create a new class (subclassed from NSObject), called LocationEntityAnnotation. Modify its header file as follows:


#import <Foundation/Foundation.h>

#import <MapKit/MapKit.h>


@interface LocationEntityAnnotation : NSObject <MKAnnotation> {

    NSString *title;

    NSString *subtitle;

    CLLocationCoordinate2D coordinate;

}


@property (nonatomic, readonly, copy) NSString *title;

@property (nonatomic, readonly, copy) NSString *subtitle;

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;


- (id)initWithCoordinate:(CLLocationCoordinate2D)newCoordinate title:(NSString *)newTitle subtitle:(NSString *)newSubtitle;


@end


Note that in the above example, the coordinate type is CLLocationCoordinate2D and there are two strings - title and subtitle.  As the protocol states that the tree properties are all readonly and copied, we set these parameters in the property definitions.  In addition, we have created our own convenience initialisation method initWithCoordinate:title:subtitle: to set the value of these properties.  Synthesise these properties in the implementation file, and add the following two methods:


- (id)initWithCoordinate:(CLLocationCoordinate2D)newCoordinate title:(NSString *)newTitle subtitle:(NSString *)newSubtitle {

   

    if ((self = [super init])) {

        title = [newTitle copy];

        subtitle = [newSubtitle copy];


        coordinate = newCoordinate;

    }

    return self;

}


- (void)dealloc

{

    [title release];

    [subtitle release];

    [super dealloc];

}


We are now ready to create annotations on our map.  Annotations are added to a map by calling the method addAnnotation: on the MKMapView object (i.e. your map).  Annotations can also be removed by calling the corresponding method removeAnnotation:.  Likewise, an array of annotations can be retrieved from the MKMapView object using the method annotations method.


Create an annotation based on the current location, set a title, and subtitle within the viewDidLoad method of the NewLocationEntryViewController class.  Then add this to the map view.  An example is give below:


    LocationEntityAnnotation *annotation = [[[LocationEntityAnnotation alloc]

                                             initWithCoordinate:[location coordinate]

                                             title:@"Current Location"

                                             subtitle:@"Add Details"] autorelease];   

    [mapView addAnnotation:annotation];       



Compile and test.


At the beginning of this step, you added the properties for the two UITextFields, and specified that the class NewLocationEntryViewController should adhere to the UITextFieldDelegate protocol.  Certain UI elements that require text to be entered or edited, such as UITextFields and UITextViews, will result in a keyboard to appear.  The keys in the keyboard, and the way in which they work is normally governed by the user’s language; for example a QWERTY keyboard would typically appear for English and some European languages; others such as French and Flemish may use the AZERTY keyboard, and languages such as Arabic, Russian, Hindi etc use completely different characters.  Languages such as Chinese (Traditional) don’t necessarily use a keyboard, but can offer the ability to select and combine strokes, or even use character recognition technology. 
Therefore, to support internationalisation, text entry elements can specify characteristics desired from the keyboard, such as having one with keys for email entry, whether or not words are capitalised, whether the keyboard should just be numeric, etc, and when selected, it is the text entry elements that result in the appropriate keyboard appearing.  In particular, this is because when the text field is selected, it becomes FirstResponder.  By resigning FirstResponder, the text field effectively dismisses the keyboard.


The UITextFieldDelegate protocol defines the method textFieldShouldReturn: which is called when the Return button is pressed.  By implementing this method in the delegate, the keyboard can be dismissed.  Add the following method to your NewLocationEntryViewController class:


- (BOOL)textFieldShouldReturn:(UITextField *)textField {

    [textField resignFirstResponder];

    return YES;

}


Finally, make sure that the NewLocationEntryViewController class is the delegate of each of the two TextViews.  You can do this with the nib file, by right clicking on each of the text views, and connecting the delegate property to the File’s Owner.  Compile the code, go to the modal view, and verify that when you type the Return key, that the keyboard disappears.