The connection calls this method each time it receives a chunk of data so you simply append the received chunk to your responseData buffer: - voidconnection:NSURLConnection *connection
Trang 1NSLog (@”connection:didReceiveResponse:”);
[self.responseData setLength:0];
}
LocationSearchViewController.m
Next, you will implement the connection:didReceiveData: method The connection calls this method each time it receives a chunk of data so you simply append the received chunk to your responseData buffer:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog (@”connection:didReceiveData:”);
// Append the received data to our responseData property [self.responseData appendData:data];
}
LocationSearchViewController.m
Now, you need to implement connectionDidFinishLoading This method runs when the connection has fi nished loading all of the requested data Here, you convert the response data to a string, clean up the connection, and call the method that you will write to parse the XML:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog (@”connectionDidFinishLoading:”);
// Convert the data to a string and log the response string NSString *responseString = [[NSString alloc]
initWithData:self.responseData encoding:NSUTF8StringEncoding];
NSLog(@”Response String: \n%@”, responseString);
[responseString release];
[connection release];
[self parseXML];
}
LocationSearchViewController.m
Finally, you will implement the connection:didFailWithError: method to log that an error occurred Remember that you will want to provide some more robust error handling and reporting
Trang 2in a production application You will also probably want to give the user some feedback as to why
the error occurred Here is the implementation:
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
NSLog (@”connection:didFailWithError:”);
NSLog (@”%@”,[error localizedDescription]);
[connection release];
}
LocationSearchViewController.m
Note that you don ’ t have to call a web service or a URL asynchronously, but it is certainly my
recommendation that you do so Alternatively, you could retrieve the XML from a URL directly
by calling the [[NSXMLParser alloc] initWithContentsOfURL] method However, using this
method will cause a loss of responsiveness in your application, as the main thread will block while
waiting for the response from the server Using the URL loading framework as you ’ ve done in this
example is asynchronous and will leave your interface responsive as the application downloads the
XML response Additionally, it gives you more control if you need to authenticate, or handle errors
more responsively as I described in the previous chapter
Defi ning the Result Class
The response XML that you receive from the web service contains a lot of information Although
you will not be using all of that information in the sample, you will parse it out and capture it To
hold this data, you will create a new class that represents an individual result Then, when you parse
the XML, you will create instances of this Result class, populate the data from the result of the
web service call, and add the Result to an array Here is the header for your Result class:
#import < Foundation/Foundation.h >
#import < MapKit/MapKit.h >
@interface Result : NSObject < MKAnnotation > {
NSString *title;
NSString *address;
NSString *city;
NSString *state;
NSString *phone;
double latitude;
double longitude;
float rating;
}
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *address;
@property (nonatomic, retain) NSString *city;
@property (nonatomic, retain) NSString *state;
@property (nonatomic, retain) NSString *phone;
@property (nonatomic) double latitude;
Trang 3@property (nonatomic) double longitude;
@property (nonatomic) float rating;
@end
Result.h
One thing to notice is that the MapKit.h header fi le is included You need to do this because you will use this class to provide annotation data for your MapView To accomplish this, you need to implement the MKAnnotation protocol To implement this protocol, you must provide a coordinate property that returns a CLLocationCoordinate2D struct This struct contains the coordinates of the point that you would like to annotate on the map You will also include a title property that will display a title for the map annotation, and a subtitle property that you will use to build the subtitle Here is the implementation for the Result class:
#import “Result.h”
@implementation Result
@synthesize title,address,city,state,phone,latitude,longitude,rating;
- (void)dealloc { [title release];
[address release];
[city release];
[state release];
[phone release];
[super dealloc];
} -(CLLocationCoordinate2D) coordinate {
CLLocationCoordinate2D retVal;
retVal.latitude = self.latitude;
retVal.longitude = self.longitude;
return retVal;
}
- (NSString *)subtitle { NSString *retVal = [[NSString alloc] initWithFormat:@”%@”,phone];
[retVal autorelease];
return retVal;
}
@end
Result.m
The dealloc method is straightforward It simply releases the memory allocated by the properties maintained by the class
Trang 4The coordinate method implements the getter for the coordinate property that is required to
implement the MKAnnotation protocol You may have noticed that a property called coordinate
was not declared, nor was a coordinate property synthesized In Objective - C, properties are simply
a convenience Behind the scenes, using properties and the dot syntax simply calls the appropriate
getter or setter methods Therefore, instead of defi ning a property, you simply implement the getter
method that the MKAnnotation protocol requires
The implementation of the coordinate method is straightforward You take the latitude
and longitude that you received from the web service call and package it up into a
CLLocationCoordinate2D struct as defi ned by the protocol Then, you just return that struct
You implement the subtitle property in the same way Instead of defi ning it as a property, you
simply implement the getter method In this case, you want the subtitle to be the phone number of
the business
Parsing the Response XML
Now that you have defi ned your Result class, you can begin parsing the response XML
and building your result set Before you start, you need to make some additions to your
LocationSearchViewController.h header fi le Add an import statement for your new
Result class:
#import “Result.h”
Add a new parseXML method declaration to the class interface:
- (void) parseXML;
Add instance variables to hold an individual result, an NSMutableArray that will hold the list of all of
the results, and an NSMutableString that will hold the characters captured during the XML parsing:
Result *aResult;
NSMutableArray *results;
NSMutableString *capturedCharacters;
Finally, defi ne a new property for the results array:
@property (nonatomic, retain) NSMutableArray *results;
Move into the LocationSearchViewController.m implementation fi le and synthesize the new
results property:
@synthesize mapView,searchBar, currentLocation,responseData,results ;
Add code to viewDidUnload and dealloc to clean up the results property:
- (void)viewDidUnload {
// Release any retained subviews of the main view
// e.g self.myOutlet = nil;
self.mapView = nil;
Trang 5self.searchBar = nil;
self.results = nil;
self.currentLocation=nil;
}
- (void)dealloc { [mapView release];
[searchBar release];
[currentLocation release];
[results release];
[super dealloc];
}
LocationSearchViewController.m
Now you are ready to implement the parseXML method You call this method from the connectionDidFinishLoading NSURLConnection delegate method when you fi nish receiving the XML response from the web service Here is the implementation:
- (void) parseXML { NSLog (@”parseXML”);
// Initialize the parser with our NSData from the RSS feed NSXMLParser *xmlParser = [[NSXMLParser alloc]
initWithData:self.responseData];
// Set the delegate to self [xmlParser setDelegate:self];
// Start the parser
if (![xmlParser parse]) {
NSLog (@”An error occurred in the parsing”);
} // Clean up the parser [xmlParser release];
}
LocationSearchViewController.m
In this method, you fi rst declare an instance of an NSXMLParser and initialize it with the response data that you received from the web service Next, you set the parser ’ s delegate to self Then, you tell the parser to start parsing the XML Finally, you release the parser
Remember that the NSXMLParser is a SAX parser, which is event driven Therefore, you need to implement the delegate methods that the parser calls as parsing events occur
Trang 6First, you will implement the didStartElement method The parser calls this method each time a
begin - element tag, such as < Title > , is found:
- (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
NSLog (@”didStartElement”);
// Check to see which element we have found
if ([elementName isEqualToString:@”Result”]) {
// Create a new Result object
aResult = [[Result alloc] init];
}
else if ([elementName isEqualToString:@”Title”]||
[elementName isEqualToString:@”Address”]||
[elementName isEqualToString:@”City”]||
[elementName isEqualToString:@”State”]||
[elementName isEqualToString:@”Phone”]||
[elementName isEqualToString:@”Latitude”]||
[elementName isEqualToString:@”Longitude”]||
[elementName isEqualToString:@”AverageRating”])
{
// Initialize the capturedCharacters instance variable
capturedCharacters = [[NSMutableString alloc] initWithCapacity:100];
}
}
LocationSearchViewController.m
In this code, you check the name of the element that you are currently processing If the element is
a Result , you create a new instance of your Result class to hold the result If the name is another
fi eld that you are interested in, you allocate and initialize the capturedCharacters instance
variable in preparation for the characters to come
Next, you will implement the foundCharacters method The parser calls this method any time
that it encounters characters inside an element You implement the foundCharacters method to
append the characters to the capturedCharacters instance variable, if the variable is not nil If
capturedCharacters is nil , you are not interested in the characters so you do nothing Here is
the code:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (capturedCharacters != nil) {
[capturedCharacters appendString:string];
}
}
LocationSearchViewController.m
Trang 7Now, you need to implement the didEndElement method The parser calls this method when an element ends This method is a bit verbose, but its functionality is straightforward When you use
a SAX parser, you will often fi nd yourself writing a function like this that has a giant if/else if block This is the nature of working with a SAX parser because you need to code one method to handle ending any element Without further ado, here is the code:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog (@”didEndElement”);
// Check to see which element we have ended
if ([elementName isEqualToString:@”Result”]) {
// Add the result to the array [results addObject:aResult];
// release the Result object [aResult release];
aResult=nil;
} else if ([elementName isEqualToString:@”Title”] & & aResult!=nil) { // Set the appropriate property
aResult.title = capturedCharacters;
} else if ([elementName isEqualToString:@”Address”] & & aResult!=nil) { // Set the appropriate property
aResult.address = capturedCharacters;
} else if ([elementName isEqualToString:@”City”] & & aResult!=nil) { // Set the appropriate property
aResult.city = capturedCharacters;
} else if ([elementName isEqualToString:@”State”] & & aResult!=nil) { // Set the appropriate property
aResult.state = capturedCharacters;
} else if ([elementName isEqualToString:@”Phone”] & & aResult!=nil) { // Set the appropriate property
aResult.phone = capturedCharacters;
} else if ([elementName isEqualToString:@”Latitude”] & & aResult!=nil) { // Set the appropriate property
aResult.latitude = [capturedCharacters doubleValue];
} else if ([elementName isEqualToString:@”Longitude”] & & aResult!=nil) { // Set the appropriate property
Trang 8aResult.longitude = [capturedCharacters doubleValue];
}
else if ([elementName isEqualToString:@”AverageRating”] & & aResult!=nil) {
// Set the appropriate property
aResult.rating = [capturedCharacters floatValue];
}
// So we don’t have to release capturedCharacters in every else if block
if ([elementName isEqualToString:@”Title”]||
[elementName isEqualToString:@”Address”]||
[elementName isEqualToString:@”City”]||
[elementName isEqualToString:@”State”]||
[elementName isEqualToString:@”Phone”]||
[elementName isEqualToString:@”Latitude”]||
[elementName isEqualToString:@”Longitude”]||
[elementName isEqualToString:@”AverageRating”])
{
// Release the capturedCharacters instance variable
[capturedCharacters release];
capturedCharacters = nil;
}
}
LocationSearchViewController.m
As I said, it ’ s verbose However, it is actually simple The fi rst part of the if statement checks
to see if you are ending a Result element If so, you add the aResult object to the results
array, release aResult , and set it to nil Every other else if clause of that if/else if block
simply sets the appropriate property of your Result object The last piece of the code releases the
capturedCharacters instance variable and sets it to nil
The last delegate method that you will implement is parserDidEndDocument The parser calls this
method when it has fi nished parsing the document In this method, you will call a method of your
class, plotResults , which will plot your results on the map:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog (@”parserDidEndDocument”);
// Plot the results on the map
[self plotResults];
}
LocationSearchViewController.m
You are now fi nished with the XML parser delegate methods Next, you will take a brief look at
MapKit and then implement the plotResults method
Trang 9Using MapKit
The MapKit framework enables you to display maps within your application You can programmatically add annotations to the map as you are doing in this example The major feature
of the framework is the MKMapView user interface control that you add to your views to make maps available in your application
You are not limited to the basic annotation styles provided by the framework You can build your own annotation view classes and use them as annotations on the map For the sake of simplicity, this example does not do that
Finally, the framework provides functionality to determine the user ’ s current location and display it
on the map You will implement this feature in the viewDidLoad method You will also implement the MKMapViewDelegate protocol to use colored pins for your annotations
To get started, you will have to modify the LocationSearchViewController.h header fi le You need to declare that you are implementing the MKMapViewDelegate protocol:
@interface LocationSearchViewController : UIViewController < CLLocationManagerDelegate,UISearchBarDelegate, MKMapViewDelegate >
LocationSearchViewController.h
Next, you will add the plotResults method to the interface:
- (void) plotResults;
You are now completely fi nished with the LocationSearchViewController.h header fi le Here is the complete header so that you can verify that your code is coordinated with the example:
#import < UIKit/UIKit.h >
#import < MapKit/MapKit.h >
#import < CoreLocation/CoreLocation.h >
#import “Result.h”
@interface LocationSearchViewController : UIViewController < CLLocationManagerDelegate,UISearchBarDelegate, MKMapViewDelegate >
MKMapView* mapView;
UISearchBar *searchBar;
CLLocation* currentLocation;
NSMutableData *responseData;
NSMutableString *capturedCharacters;
Result *aResult;
NSMutableArray *results;
}
@property (nonatomic, retain) IBOutlet MKMapView* mapView;
@property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
@property (nonatomic, retain) CLLocation* currentLocation;
Trang 10@property (nonatomic, retain) NSMutableData *responseData;
@property (nonatomic, retain) NSMutableArray *results;
- (void) parseXML;
- (void) plotResults;
@end
LocationSearchViewController.h
Now you need to move into the implementation fi le The fi rst thing that you want to do is center
the map on the current location of the device To do this, you will implement the Core Location
delegate method locationManager:didUpdateToLocation:fromLocation: If you recall from the
section entitled “ Core Location, ” the location manager calls this method when it determines that
the device has moved Here is the complete implementation:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
self.currentLocation = newLocation;
// Create a mapkit region based on the location
// Span defines the area covered by the map in degrees
MKCoordinateSpan span;
span.latitudeDelta = 0.05;
span.longitudeDelta = 0.05;
// Region struct defines the map to show based on center coordinate and span
MKCoordinateRegion region;
region.center = newLocation.coordinate;
region.span = span;
// Update the map to display the current location
[mapView setRegion:region animated:YES];
// Stop core location services to conserve battery
[manager stopUpdatingLocation];
}
LocationSearchViewController.m
First, you will set the currentLocation property to the current location of the device Then,
you create an MKCoordinateSpan struct This struct defi nes the area that you want to display on
the map You are declaring that you would like the map to display 0.05 degrees of latitude and
longitude The span determines how far in you want to zoom the map A larger span results in a
larger area displayed on the map, thus a lower zoom factor A small span zooms in on a small area
therefore producing a high zoom factor