Chapter 11. Integrating with Web Services

WHAT'S IN THIS CHAPTER?

  • Learning what a web service is and its usefulness

  • Understanding how to send data to an XML web service using the POST and GET methods

  • Parsing the response messages from a web service and using the resulting data from within an iPhone SDK application

  • Building location-based applications using the GPS functionality in the Core Location framework and the mapping capability of the MapKit framework

In the previous chapter, you learned how to ask for data from a web server by sending HTTP requests. You also learned about XML and how to parse the XML that you receive from web servers in response to your HTTP requests. In this section, you will take that knowledge a step further by integrating your application with Internet-based web services.

In this chapter, you will learn about web services and how to use them in your iPhone and iPad applications. You will also explore a couple of other interesting features of the iPhone SDK, including MapKit and Core Location.

This chapter builds on the knowledge that you gained from the last chapter to integrate iPhone SDK applications with web services. I will cover the use of the GET and POST HTTP methods to send data to a web service. You will then build a couple of sample applications that use web services as an integral part of the program.

NETWORK APPLICATION ARCHITECTURE

When you set out to start work on a new project, one of your first decisions involves designing the architecture of your application and determining how the parts of your system will work together. Most data-oriented applications will involve a database, and all iPhone applications will include a user interface. In order to get a better understanding of how to design applications where the iPhone and database need to communicate, you will take a brief look at a couple of typical network application architectures.

Two-Tier Architecture

The simplest network application architecture consists of a database and an interface. Architects refer to this design as two-tier or client-server architecture. You can see this design in Figure 11-1. In this design, the client application resides on the mobile device or computer, and implements the application interface. Additionally, the client software includes the implementation of the business rules that govern the functionality of the application. The server tier is generally an enterprise database such as Oracle, SQLServer, or MySQL, which can share its data with many connected clients.

Two-tier architecture

Figure 11.1. Two-tier architecture

You see this type of client-server architecture when you look at web servers, particularly web servers that serve simple HTML web pages. The client in this design is your web browser and the server is the web server. Many clients connect to the same web server to get data. The client web browser hosts the user interface and the logic to display the HTML data.

This architecture is most appropriate for simple applications. However, as the business rules for your application become more complex, you will likely outgrow this design. In order to maintain encapsulation of functionality, the client should be responsible for displaying data and the server for storing it. If there are minimal rules that govern the logic and data of the application, you could implement them either in the database or on the client.

As the number and complexity of the rules that define the functionality of your application increase, you will need to decide where you want to implement those rules. You could implement them on the client. If your client software were a "thick" desktop application, changing rules would mean re-deploying your application to your clients. If the client is a "thin" web-browser based client, you are constrained by the capabilities and performance of the browser and client-side languages such as JavaScript.

As the business rules of your application get more complex, you will often need to add a third tier to your application architecture. It is in this tier where the business rules reside. In this design, you can decouple your business rules from both the display logic of the client software and the storage system of the database.

Three-Tier Architecture (n-tier)

As you learned in the last section, you need to consider your application's ability to maintain a complex set of business rules in your design. As you design your system, you need to consider the complexity of the rules that govern your application and the likelihood that these rules could change. For instance, if you are building an order entry system, you need to consider the fact that customer discount percentages could change. You may not want to code these percentages into the user interface layer of your application because any changes to this data would require a redeployment of your software. In instances like this, it is common for the designer to pull these business rules out of the interface and place them in their own logical software layer. The client-server design then evolves into a three-tier (or n-tier if more than one business layer is necessary) architecture (see Figure 11-2).

Three-tier architecture

Figure 11.2. Three-tier architecture

In a three-tier design, the application architect moves the business logic in to its own logical and perhaps physical process. You can therefore decouple the business rules from the user interface and the database. This gives the designer more flexibility when it comes to designing the database, the interface, and the business logic. It also effectively encapsulates each of these important features of a system. It is easier to manage the business logic of your system if you do not tightly couple this code to the database or the user interface. This simplifies making changes to any one of the layers of the design without affecting any of the other layers. In this design, all of the rules that govern what data can be added to the database and specific calculations that are performed on that data are moved out of the interface layer into their own tier. Many commercial application servers such as WebLogic, Apache Tomcat, and WebSphere perform this function.

Application Communication

Client applications need to be able to talk to the business tier or application server and the application server needs to be able to talk to the database.

Typically, the application server and database reside on the same local network, although this is not necessary. These two layers generally communicate through an interface layer that knows how to call functions in the database. A common interface API for database communication is Open Database Connectivity (ODBC). Most programming languages provide support APIs for connecting to databases as well. The Microsoft .NET platform provides an API called ADO.NET while the Java system provides JDBC.

For the client to business-tier communication, the designer can implement connectivity using a variety of methods depending on the languages and operating systems used in the implementation.

In the past, designers of Windows systems would have used DCOM or Distributed Component Object Model. This technology allowed components on different computers to talk to each other. In a Java-based system, the designer could choose a similar technology called Java Remote Method Invocation (RMI). In this system, objects running in one Java virtual machine can call methods on objects in another virtual machine. CORBA, or Common Object Request Broker Architecture, is another technology that enables remote machines to execute methods and share data with one another. The advantage of CORBA is that it is language independent. There are CORBA implementations for most major computer languages and operating systems.

A more current technology enables the designer to implement the functionality of the business tier as a web service. With this technology, the designer can expose the business logic of the application using a web server. Client software can then use well-known methods of the HTTP protocol to access the services exposed by the application server.

INTRODUCING WEB SERVICES

When you use a web browser to visit your favorite web site, you begin by sending a request to the site's server. If all goes well, the server accepts your request and returns a response. The response that you get is text in the form of HTML, which your browser renders into the page that you see on your screen.

Consumers of the HTML that a web server sends are generally people that view the HTML using a browser. Web services, on the other hand, respond to requests by sending back data that is intended for computers to consume.

Typically, most web services default to returning response data to you using XML. With some web services, however, you can request that your data be returned in other formats, such as JSON (JavaScript Object Notation). JSON is a text-based format, like XML. Unlike XML, however, JSON can be used directly in browser-based web applications that are backed by JavaScript code. Coverage of JavaScript is beyond the scope of this book, as is using JSON in browser-based applications. What you should know though is that there are parsers available to consume JSON response data for many languages, including Objective-C.

In the previous chapter, you looked at a snippet of XML that could be used to add an address entity to an address book application:

<address>
    <name>John Smith</name>
    <street>1 Ocean Blvd</street>
    <city>Isle Of Palms</city>
    <state>SC</state>
    <zip>29451</zip>
</address>

Here is the JSON representation of the same address:

{
    "name": "John Smith",
    "street": "1 Ocean Blvd",
    "city": "Isle Of Palms",
    "state": "SC",
    "zip": "29451"
}

Unlike XML, whose structure is defined by its users, JSON relies on data structures defined in JavaScript. Specifically, JSON can use one of two data structures: the Object or the Array. Because these data structures are common in almost all modern languages, it is easy to convert JSON messages into data structures that can be used in almost any language. The Object consists of a set of name-value pairs, and the Array is a list of values.

Application architects can design web services to act as the application server for their particular application. Third parties also build web services to provide specific services to their clients. In this chapter, you will develop two applications that use third-party web services from Yahoo! to implement some interesting functionality. The first service allows you to submit queries and returns the results based on proximity to the location of the search. The second service accepts a long string of text and returns a list of the most important words in the text.

In general, web services provide a way to expose some complex logic by using the simple and well-known interfaces of the Web.

For example, a designer can easily expose an external interface to a database by building a web service that allows clients to create records or query the data store. Consider the product catalog from Part I of this book. You could build a web service that exposes an interface that accepts orders and enters them directly into the back-end database. You could then build an iPhone order entry application that uses this web service to allow your sales force in the field to submit orders.

You can develop web services using many languages and frameworks such as Java, Microsoft.NET, and Ruby on Rails. Because of the variation in languages and platforms available, building a web service is beyond the scope of this book. This chapter teaches you how to integrate your application with existing web services.

SOAP Messaging

There are two predominant protocols used for exchanging data with web services: SOAP and REST. You will look at SOAP in this section and examine REST in the next.

Microsoft originally developed SOAP (Simple Object Access Protocol) in the late 90s and the W3C standards body has standardized the protocol. The designers built SOAP to be platform- and language-agnostic in order to replace technologies such as CORBA and DCOM. The designers chose XML as the format for the messages in an effort to avoid some of the problems associated with the binary-based messaging systems that SOAP would replace.

Unlike CORBA and DCOM, the designers of SOAP decided to send messages using the HTTP protocol, which makes network configuration easy. If users could see each other over the Web using a browser, they could send messages to each other using SOAP.

SOAP messages share a common element with other messaging formats such as HTML in that the message contains a header and a body. The header contains details about the type of message, format of the data, and so on. The body holds the "payload" or the data that the user intends to transmit.

One issue with using SOAP is the complexity of the messages. In order to transmit a little bit of data, users of SOAP often need to transmit long messages. For example, here is the SOAP request message that you would send to the eBay SOAP API to obtain the official eBay time (from eBay.com: using the eBay API to get the official eBay time):

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Header>
    <RequesterCredentials soapenv:mustUnderstand="0"
        xmlns="urn:ebay:apis:eBLBaseComponents">
       <eBayAuthToken>ABC...123</eBayAuthToken>
       <ns:Credentials xmlns:ns="urn:ebay:apis:eBLBaseComponents">
        <ns:DevId>someDevId</ns:DevId>
        <ns:AppId>someAppId</ns:AppId>
        <ns:AuthCert>someAuthCert</ns:AuthCert>
       </ns:Credentials>
    </RequesterCredentials>
  </soapenv:Header>
  <soapenv:Body>
    <GeteBayOfficialTimeRequest xmlns="urn:ebay:apis:eBLBaseComponents">
<ns1:Version xmlns:ns1="urn:ebay:apis:eBLBaseComponents">405</ns1:Version>
    </GeteBayOfficialTimeRequest>
  </soapenv:Body>
</soapenv:Envelope>

This is quite a verbose message, especially considering that you are not even passing in any parameters. Although the API simply returns a timestamp that represents the current time, it is also quite verbose:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <GeteBayOfficialTimeResponse xmlns="urn:ebay:apis:eBLBaseComponents">
      <Timestamp>2005-05-02T00:07:22.895Z</Timestamp>
      <Ack>Success</Ack>
      <CorrelationID>
        00000000-00000000-00000000-00000000-00000000-00000000-0000000000
      </CorrelationID>
      <Version>405</Version>
      <Build>20050422132524</Build>
    </GeteBayOfficialTimeResponse>
  </soapenv:Body>
</soapenv:Envelope>

While SOAP offers far more functionality than simple method calling, you can see why developers often mock the name "Simple" Object Access Protocol. Designers that simply needed to pass data between application layers or make basic method calls found that SOAP was overkill. This led to the development of a simpler messaging protocol called REST.

The REST Protocol

REST stands for REpresentational State Transfer. The designers of REST wanted to be able to call web service functions using the simple and well-known methods exposed by the HTTP protocol: GET and POST. The goal was to make it easy to invoke remote functions and provide response data in a more manageable format.

REST uses a simple URI scheme to communicate with web services. Developers who are comfortable using HTTP to make requests over the Web already know how to call REST services. Typically, a client application can simply issue an HTTP GET request to a URL to make a method call. The developer can pass parameters to the call using querystring parameters as if simply passing parameters to a regular web site.

Because REST is not standardized, it is possible that the client will receive response data from a REST-based service in an arbitrary format. However, XML is commonly used. Typically, REST-based web services can also return data using the JSON format instead of XML. This is preferable if you are developing Web-based applications.

In order to be able to easily determine the intent of a call, the HTTP GET method is used to query for data, and the HTTP POST method is used when you want to modify data such as in an INSERT or UPDATE to a database. This makes it easy to determine if the caller intends to simply query the service for data or make changes to the data managed by the service.

It is very easy to build simple web services using REST. If you already know how to implement server-side web scripts, REST implementations are typically straightforward. Both of the examples in this chapter will call web services based on the REST protocol. All of the concepts in this chapter are equally applicable if your application needs to call a SOAP-based service. The only real difference is the format of the message that you are sending and the format of the response message.

EXAMPLE 1: LOCATION-BASED SEARCH

One of the great features of the iPhone and iPad 3G is the GPS. Developers are able use GPS to determine the current location of the device and build applications that use this data to provide a wealth of information to users. This leads to some very creative applications.

In the first example in this chapter, you will take advantage of this capability of the device. You will use the Core Location framework to determine the current position of the device. Then, you will allow the user to input search terms into the application. You will then call a Yahoo! web service using the REST protocol and the HTTP GET method to obtain businesses that meet the search criteria and are close to the current location. Finally, you will use the MapKit framework to display the returned data on a map.

Note

Don't worry if you don't have an iPhone. When you use Core Location with the iPhone simulator, the simulator conveniently returns the latitude and longitude of Apple headquarters in Cupertino, California.

The finished application will look like Figure 11-3.

Starting Out

Now that you have an idea of what the application will do and how it will look, let's get started. The first step, as always, is to open Xcode and create a new project. Because there will be only one view, you should create a new View-based application. Call the new application LocationSearch.

You will be using the Core Location and MapKit frameworks in this example. These frameworks are not included in iPhone projects by default, so you will have to add references to the frameworks to the project. Right-click on the Frameworks folder in the Groups & Files pane in Xcode. From the pop-up menu, select Add

Starting Out
Completed LocationSearch application

Figure 11.3. Completed LocationSearch application

You will be using these frameworks in your LocationSearchViewController class. Therefore, you will need to import the headers for these frameworks in the LocationSearchViewController.h header. Add the following import statements to the LocationSearchViewController.h header file:

#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
                                                         
Completed LocationSearch application

The user interface for the application consists of a search bar to accept the search criteria from the user and a map view that you will use to display the search results. Because you need access to both of these interface items in your code, you will need to add instance variables and outlets for these elements.

In the LocationSearchViewController.h header, add instance variables for an MKMapView* called mapView and a UISearchBar* called searchBar to the interface definition:

MKMapView* mapView;
    UISearchBar *searchBar;
                                                         
Completed LocationSearch application

Now that you have declared your instance variables, declare IBOutlet properties for these UI elements so that you have access to them in Interface Builder:

@property (nonatomic, retain) IBOutlet MKMapView* mapView;
@property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
                                                         
Completed LocationSearch application

Next, you will move into the implementation file LocationSearchViewController.m. You first need to synthesize your two new properties. Add a line to synthesize the mapView and searchBar properties to the implementation:

@synthesize mapView,searchBar;

Finally, any time that you use a property that retains its value, you need to clean up the memory used by the property in the dealloc and viewDidUnload methods. Add the code to set the outlet properties to nil in the viewDidUnload method:

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.mapView = nil;
    self.searchBar = nil;
}
                                                         
Completed LocationSearch application

Also, add the code to release the instance variables in the dealloc method:

- (void)dealloc {
    [mapView release];
    [searchBar release];
    [super dealloc];
}
                                                         
Completed LocationSearch application

Building the Interface

This application will have two elements in the interface, an MKMapView and a UISearchBar. Double-click on the LocationSearchViewController.xib file in the Resources folder of the Groups & Files pane in Xcode. This launches Interface Builder and opens the LocationSearchViewController.xib file.

In Interface Builder, make sure that you have the Library palette open. If you don't, you can open it from the menu bar by selecting Tools

Building the Interface

Next, locate the map view under Library

Building the Interface

Now that you have visually defined your user interface, you need to connect the user interface elements to the code. Hook up the UISearchBar to the searchBar outlet in File's Owner. Likewise, hook up the MKMapView to the mapView outlet in File's Owner.

That's all that you will need to do in Interface Builder so, you can close the LocationSearchViewController.xib file and quit Interface Builder.

LocationSearchViewController in Interface Builder

Figure 11.4. LocationSearchViewController in Interface Builder

Core Location

The key ingredient in building a location-based application is getting the location of the user. Apple has built the Core Location framework to enable your applications to interface with the GPS hardware in the device. Using this framework, you can obtain the current location or heading of the device.

The Core Location Framework

Core Location is an asynchronous API that uses delegation to report location information for the device. To use this functionality, you first need to instantiate an instance of the CLLocationManager class. As the name implies, you use this class to manage the Core Location functionality. The class contains methods to start and stop location and heading updates as well as a property that returns the location of the device.

Once you have instantiated your CLLocationManager instance, you need to define a delegate. The delegate must conform to the CLLocationManagerDelegate protocol. This protocol defines methods that allow you to respond to location and heading change events as well as errors. You will typically write code in the locationManager:didUpdateToLocation:fromLocation: method to respond to changes in the device location.

In a production application, you should also implement the locationManager:didFailWithError: method. Core Location calls this method in case of error. Additionally, Core Location will automatically prompt the user to determine if he wants to allow your application to access their current location. If the user decides not to make this information available, Core Location will call this error method. Your application should be able to gracefully handle this situation.

After you have implemented the Core Location delegate methods and set the CLLocationManager's delegate, you will typically tell the manager to start updating the device's location. Core Location will return the location of the device as quickly as possible, often using a cached value if one is available. After the initial call, the device will continue to hone the position based on a value that you can set using the desiredAccuracy property. You can control the number of callbacks that you receive to the locationManager:didUpdateToLocation:fromLocation: method by setting the distanceFilter property. This property allows you to set the minimum distance that the device must move before the framework calls the method again.

Although the example will not use it, Core Location can also report heading information if the hardware of the device supports it. If you choose to enable heading data, your Core Location will call the locationManager:didUpdateHeading: method to report heading updates.

There are a few considerations to be aware of when developing software with Core Location. First, you should use the lowest level of accuracy that your application needs in order to implement its functionality. You can specify that the framework determine the device's location to the nearest three kilometers, one kilometer, one hundred meters, ten meters, or best possible accuracy. The default value is best possible accuracy. The GPS needs more power to determine the location of the device with higher precision. Power consumption on a mobile device is something that you should consider when building mobile applications. Therefore, you should always specify the least accuracy that you can accept while still providing the desired functionality in your application.

Another consideration is when to turn off Core Location. As I mentioned, using the GPS chip consumes a substantial amount of power. You should turn off location updates using the stopUpdatingLocation method as soon as is practical for your application. In the application, you only need to get the initial location of the device in order to perform your location search, so you will turn off location updates after you determine the device's location. Occasionally, you will need to obtain frequent location updates while your application is running — for example, when the application is providing turn-by-turn directions. Just keep in mind that using the GPS consumes a lot of power and that you should keep it enabled for the shortest possible length of time.

Using Core Location

Now that you have an idea of how to use Core Location, you will add Core Location functionality to your application. In the LocationSearchViewController.h header file, update the interface declaration to indicate that you will be implementing the CLLocationManagerDelegate protocol:

@interface LocationSearchViewController
: UIViewController <CLLocationManagerDelegate>
                                                         
Using Core Location

You will want to maintain a copy of the current location of the device. So, in the LocationSearchViewController.h header, add an instance variable for the current location:

CLLocation* currentLocation;

You will also add a property that references the current location:

@property (nonatomic, retain) CLLocation* currentLocation;

In the implementation file, synthesize the currentLocation property:

@synthesize mapView,searchBar, currentLocation;

Next, add code to clean up the instance variable and property in the dealloc and viewDidUnload methods:

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.mapView = nil;
    self.searchBar = nil;
    self.currentLocation=nil;
}


- (void)dealloc {
    [mapView release];
    [searchBar release];
    [currentLocation release];
    [super dealloc];
}
                                                         
Using Core Location

Now, you will add code to the viewDidLoad method to create the CLLocationManager:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Create the Core Location CLLocationManager
    CLLocationManager *locationManager = [[CLLocationManager alloc] init];
    // Set the delegate to self
    [locationManager setDelegate:self];
    // Tell the location manager to start updating the location
    [locationManager startUpdatingLocation];

}
                                                         
Using Core Location

In this code, you first allocate and initialize a CLLocationManager object. The CLLocationManager object controls all communication with the GPS in the device. Next, you set the delegate for the location manager to self. You can do this because you have declared that your class will implement the CLLocationManagerDelegate protocol. Finally, the code tells the location manager to start updating the device location using the GPS.

The final step is to implement the Core Location delegate methods. For now, you will simply implement the locationManager:didUpdateToLocation:fromLocation: method to store the location of the device in the currentLocation property. Additionally, you will implement locationManager:didFailWithError: to log that you received an error. Here is the implementation:

// Called when the location manager determines that there is a new location
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation {
    self.currentLocation = newLocation;

}

// Called when an error occurs
- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error {
    NSLog (@"locationManager:didFailWithError");
}
                                                         
Using Core Location

The Local Search API

You will be using the Yahoo! local search service API to get your search results. This is a REST-based API. The URL for the web service is: http://local.yahooapis.com/LocalSearchService/V3/localSearch.

This service enables you to search for businesses near a given location. The results include the name of the business, latitude and longitude, and Yahoo! user ratings for the business.

The search API can accept many different parameters to help you narrow and filter your search. These include the radius from the base point to search for results, a route along which to search, or a specific category in which to search. To keep this sample simple, you will only pass in the latitude and longitude of the device as the location to search and the query search terms that you are looking for.

The XML that you get in response to a query request will look something like this:

<?xml version="1.0"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="urn:yahoo:lcl"
    xsi:schemaLocation="urn:yahoo:lcl
    http://local.yahooapis.com/LocalSearchService/V3/LocalSearchResponse.xsd"
    totalResultsAvailable="2501" totalResultsReturned="10"
    firstResultPosition="1">
    <ResultSetMapUrl>
        http://maps.yahoo.com/broadband/?tt=pizza&amp;tp=1
    </ResultSetMapUrl>
    <Result id="21566059">
        <Title>Ciceros Pizza</Title>
        <Address>20010 Stevens Creek Blvd</Address>
        <City>Cupertino</City>
        <State>CA</State>
        <Phone>(408) 253-2226</Phone>
        <Latitude>37.322724</Latitude>
        <Longitude>-122.023665</Longitude>
        <Rating>
            <AverageRating>4.5</AverageRating>
            <TotalRatings>9</TotalRatings>
            <TotalReviews>5</TotalReviews>
            <LastReviewDate>1266107776</LastReviewDate>
            <LastReviewIntro>
                My favorite pizza in the world. I understand that everybody has
                personal preferences when it comes to pizza, but for me Cicero's
                is the best. I've had pizza in Italy, many places in Europe,
                New York and everywhere else I've traveled to. For me, the best
                pizza in the world is in Cupertino, Ca..
            </LastReviewIntro>
        </Rating>
        <Distance>0.73</Distance>
        <Url>http://local.yahoo.com/info-21566059-ciceros-pizza-cupertino</Url>
        <ClickUrl>
            http://local.yahoo.com/info-21566059-ciceros-pizza-cupertino
        </ClickUrl>
        <MapUrl>
            http://maps.yahoo.com/maps_result?q1=20010+Stevens+Creek+Blvd
            +Cupertino+CA&amp;gid1=21566059
        </MapUrl>
        <Categories>
            <Category id="96926234">Carry Out &amp; Take Out</Category>
            <Category id="96926236">Restaurants</Category>
            <Category id="96926243">Pizza</Category>
        </Categories>
    </Result>
</ResultSet>

In this instance, you sent a query for "pizza" with the latitude and longitude of Apple headquarters in Cupertino, CA. This XML represents the first result returned from the web service. You can see that the response includes relevant information such as the name and address of the business, the phone number, the latitude and longitude, and the distance from the origin of the query. The response also contains review information submitted to Yahoo! by users of their web search services. Finally, you can see that there are several URLs if you wanted to use this information to allow a user to click on a link in your application to bring up a map or to directly link to the site of the establishment.

If you look at the ResultSet element at the top of the XML, you will notice that it has a few attributes of interest: totalResultsAvailable, totalResultsReturned, and firstResultPosition. Because there may be a very large number of results for a query, the service returns the result set in batches. You can specify the batch size, up to 20 results at a time. You can also specify the start position of the results that you want to retrieve. In this particular case, there were 2,501 results, of which you received the first 10. Your application will only handle the first batch of results. However, in a production application, you will probably want to write some code to resend the query more than once, in order to retrieve more results. It is up to you to keep track of the result position and to send back the next starting position to the service. Keep in mind that web services are stateless. They typically do not maintain any state on the server regarding your last request. It is up to you to implement whatever paging functionality meets the requirements of your application.

You can find complete documentation of the API at http://developer.yahoo.com/search/local/V3/localSearch.html.

Using the Search Bar

This example added a UISearchBar control to your application. You will use this widget to get the search criteria from the user. Unlike the other user interface elements that you have used thus far, the search bar works using the delegation pattern. Therefore, you need to declare your class as the delegate for the search bar and implement the UISearchBarDelegate protocol. Then, when the search bar text changes or the user presses buttons, the search bar will call your code through the delegate methods.

In your LocationSearchViewController.h header file, you need to declare that you will implement the UISearchBarDelegate protocol. Change the interface declaration to include this protocol:

@interface LocationSearchViewController
: UIViewController <CLLocationManagerDelegate,UISearchBarDelegate>
                                                         
Using the Search Bar

Now, you need to tell the searchBar property that you want the LocationSearchViewController to be its delegate. Add the following code to the viewDidLoad method:

// Set the delegate for the searchbar
    [self.searchBar setDelegate:self];
                                                         
Using the Search Bar

Next, you will write some code to handle the delegate events you are interested in. When a user taps the search button, you will take the text of the search bar and pass it to the location search web service. After submitting the request, you will receive callbacks from the NSURLConnection using its delegate methods. In order to hold on to the response data that you receive from the connection, you will set up an instance variable and property called responseData. Add an instance variable called responseData of type NSMutableData* to your LocationSearchViewController.h header file:

NSMutableData *responseData;

Now, add a corresponding property:

@property (nonatomic, retain) NSMutableData *responseData;

Synthesize the new property in the implementation file:

@synthesize mapView,searchBar, currentLocation,responseData;

You are now ready to implement your searchBar delegate methods. The search bar calls the first method, searchBarSearchButtonClicked:, when the user clicks the Search button. In this method, you will create a request using the text in the search bar and send it off to the web service for processing. Here is the code:

- (void)searchBarSearchButtonClicked:(UISearchBar *)localSearchBar {
    NSLog (@"searchBarSearchButtonClicked");

    // Construct the URL to call
    // Note that you have to add percent escapes to the string to pass it
    // via a URL
    NSString *urlString = [NSString
        stringWithFormat:
            @"http://local.yahooapis.com/LocalSearchService/V3/localSearch?"
              "appid=YOUR_ID_GOES_HERE&query=%@&latitude=%f&longitude=%f",
        [localSearchBar.text
         stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding],
        self.currentLocation.coordinate.latitude,
                           self.currentLocation.coordinate.longitude];

    // Log the string that we plan to send
    NSLog (@"sending: %@",urlString);

    NSURL *serviceURL = [NSURL
                         URLWithString:urlString];

    // Create the Request.
    NSURLRequest *request = [NSURLRequest
                             requestWithURL:serviceURL
                             cachePolicy:NSURLRequestUseProtocolCachePolicy
                             timeoutInterval: 30.0];

    // Create the connection and send the request
    NSURLConnection *connection =
[[NSURLConnection alloc] initWithRequest:request delegate:self];

    // Make sure that the connection is good
    if (connection) {
        // Instantiate the responseData data structure to store to response
        self.responseData = [NSMutableData data];

    }
    else {
        NSLog (@"The connection failed");
    }

    [localSearchBar resignFirstResponder];
}
                                                         
Using the Search Bar

You are using the same technique to send a request and receive a response from a web server, as I introduced in the previous chapter. I will point out a few minor differences.

First, you create a string that represents the URL that you will be calling. You are using the stringWithFormat method because you will be plugging the search query text, latitude, and longitude in dynamically at runtime. Notice that you are using the standard HTTP method for passing parameters on the querystring. That is, you give the name of the URL (http://local.yahooapis.com/LocalSearchService/V3/localSearch) and append a question mark (?) to indicate that parameters follow. Parameters are then passed as name-value pairs using the format parameterName=value. You separate your parameter pairs using the ampersand (&) character.

You can see from your URL string that you are passing four parameters: appid, query, latitude, and longitude. The latitude and longitude are the coordinates around which you want to center your search. The query is the item for which you want to search. The appid is a token that you receive from Yahoo! when you sign up to be able to use their web service. You can obtain a token to use the Local Search Service by going to https://developer.apps.yahoo.com/wsregapp/. Make sure that you replace the text, YOUR_ID_GOES_HERE in the definition of the urlString variable in the preceding code with the appid that you receive from Yahoo!, or you will not be able to access the web service.

Another important thing to notice when examining your URL string is that you call the method stringByAddingPercentEscapesUsingEncoding on the text string that you obtain from the search bar. The HTTP protocol uses characters such as & and % in very specific ways. If your text contains those (and several other) characters, they need to be "URL encoded." Calling this method on your string ensures that you are properly formatting the string to be able to pass it as a querystring parameter.

The last part of constructing your string is to get the latitude and longitude from your currentLocation object and replace them in the URL.

The next line simply logs the string to the console so that you can verify that you are creating the request URL string properly.

Next, you go on to create the objects that you need to submit your query to the web service. You create an NSURL using the URL string that you built earlier. Then, you create an NSURLRequest with the NSURL and specify the cache policy and timeout that you want. Finally, you create your NSURLConnection with the request, setting the delegate of the connection object to self.

Once you validate that the connection object is good, you instantiate the responseData property. Finally, you send the resignFirstResponder message to the search bar. This tells the search bar to dismiss the associated keyboard.

The next bit of code you will write will implement the searchBar:textDidChange: delegate method. The search bar calls this method any time the text in the search bar changes. Later on, you will implement this function to remove the annotations from the MapView if the search is changing. For now, you will simply log a message indicating that someone called the method:

// Called when the searchbar text changes
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
    NSLog (@"textDidChange");

}
                                                         
Using the Search Bar

Handling the Web Service Response

In this section, you will take a look at the code that you need to write to handle the response from the web service. First, you will implement the NSURLConnection delegate methods to deal with the raw data that you receive from the web service. Then, you will define a new Result class that will hold data about each result returned from the service. Next, you will parse the XML and generate an array of Result objects. Finally, you will use the MapKit API to plot the results on a map.

The NSURLConnection Delegate Methods

In the previous section, you wrote code to handle the user clicking the search bar. The code constructs the URL and uses the HTTP GET method to send the request to the web service. Now, you need to implement the NSURLConnection delegate methods to handle the response data that the web service returns from your request. This code is very similar to the code that you built in the last chapter. To simplify this sample and focus on the web service–related aspects, I have removed some of the delegate methods that you will not need to handle.

First, you will want to implement the connection:didReceiveResponse: method. The NSURLConnection calls this delegate method when there is enough data to create the response. The connection could call this method multiple times in cases where there are server redirects from address to address. Therefore, you need to reset your response data each time the connection calls this method. Here is the implementation:

- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
NSLog (@"connection:didReceiveResponse:");

    [self.responseData setLength:0];

}
                                                         
The NSURLConnection Delegate Methods

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

}
                                                         
The NSURLConnection Delegate Methods

Now, you need to implement connectionDidFinishLoading. This method runs when the connection has finished 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: 
%@", responseString);

    [responseString release];

    [connection release];

    [self parseXML];
}
                                                         
The NSURLConnection Delegate Methods

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 in 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];

}
                                                         
The NSURLConnection Delegate Methods

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.

Defining 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;
@property (nonatomic) double longitude;
@property (nonatomic) float rating;

@end
                                                         
Defining the Result Class

One thing to notice is that the MapKit.h header file 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
                                                         
Defining the Result Class

The dealloc method is straightforward. It simply releases the memory allocated by the properties maintained by the class.

The 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 defining 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 defined by the protocol. Then, you just return that struct.

You implement the subtitle property in the same way. Instead of defining 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 defined 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 file. 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, define a new property for the results array:

@property (nonatomic, retain) NSMutableArray *results;

Move into the LocationSearchViewController.m implementation file 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;
self.searchBar = nil;
    self.results = nil;
    self.currentLocation=nil;
}


- (void)dealloc {
    [mapView release];
    [searchBar release];
    [currentLocation release];
    [results release];
    [super dealloc];
}
                                                         
Parsing the Response XML

Now you are ready to implement the parseXML method. You call this method from the connectionDidFinishLoading NSURLConnection delegate method when you finish 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];


}
                                                         
Parsing the Response XML

In this method, you first 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.

First, 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];
    }
}
                                                         
Parsing the Response XML

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 field 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];
    }
}
                                                         
Parsing the Response XML

Now, 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 find 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
aResult.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;
    }

}
                                                         
Parsing the Response XML

As I said, it's verbose. However, it is actually simple. The first 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 finished 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];
}
                                                         
Parsing the Response XML

You are now finished with the XML parser delegate methods. Next, you will take a brief look at MapKit and then implement the plotResults method.

Using 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 file. You need to declare that you are implementing the MKMapViewDelegate protocol:

@interface LocationSearchViewController
: UIViewController <CLLocationManagerDelegate,UISearchBarDelegate,
    MKMapViewDelegate>
                                                         
Using MapKit

Next, you will add the plotResults method to the interface:

- (void) plotResults;

You are now completely finished with the LocationSearchViewController.h header file. 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;
@property (nonatomic, retain) NSMutableData *responseData;
@property (nonatomic, retain) NSMutableArray *results;


- (void) parseXML;
- (void) plotResults;


@end
                                                         
Using MapKit

Now you need to move into the implementation file. The first 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];

}
                                                         
Using MapKit

First, you will set the currentLocation property to the current location of the device. Then, you create an MKCoordinateSpan struct. This struct defines 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.

Next, you define an MKCoordinateRegion struct. You will pass this struct to the mapView to define the region that you want to display. An MKCoordinateRegion consists of a span and a center point. You will center the map on the coordinate that you receive from Core Location.

Next, you tell the mapView to set the region displayed on the map and to animate the transition to your new region. Finally, you tell the Core Location manager to stop getting location updates from the GPS. Because your application does not need extremely accurate resolution, nor do you need constant updates from the GPS, you can conserve power by turning the GPS off.

For the next step, you need to make a couple of additions to the viewDidLoad method. Because you will be customizing the pin colors for your annotations, you need to set the mapView delegate to self. In addition, for illustrative purposes, you will display the user's location on the map by setting the map view's showsUserLocation property to YES. When using the showsUserLocation property, you need to be aware that this will cause the map view to use Core Location to retrieve and maintain the user's location on the map. This forces the GPS receiver to remain on, consuming valuable battery power. You should carefully consider if your application needs this functionality or not before using it. This example uses this feature to demonstrate a capability of the map view to display the user's current location. Here is the complete implementation of viewDidLoad:

- (void)viewDidLoad {
    [super viewDidLoad];


    // Create the results array
    self.results = [[NSMutableArray alloc] init];


    // Create the Core Location CLLocationManager
    CLLocationManager *locationManager = [[CLLocationManager alloc] init];
    // Set the delegate to self
    [locationManager setDelegate:self];
    // Tell the location manager to start updating the location
    [locationManager startUpdatingLocation];

    // Set the delegate for the searchbar
    [self.searchBar setDelegate:self];

    // Set the delegate for the mapView
    [self.mapView setDelegate:self];

    // Use Core Location to find the user's location and display it on the map
    // Be careful when using this because it causes the mapview to continue to
    // use Core Location to keep the user's position on the map up to date
    self.mapView.showsUserLocation = YES;

}
                                                         
Using MapKit

When the user clears the text from the search bar, you want to clear the old annotations from the map. You can do this by implementing the searchBar:textDidChange: delegate method like this:

// Called when the searchbar text changes
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
    NSLog (@"textDidChange");

    // If the text was cleared, clear the map annotations
    if ([searchText isEqualToString:@""])
    {
        // Clear the annotations
        [self.mapView removeAnnotations:self.mapView.annotations];

        // Clear the results array
        [self.results removeAllObjects];
    }

}
                                                         
Using MapKit

You implement this code to check to see if the user has cleared the search string. If he has, you remove the annotations from the map and clear your results array.

In the last bit of code, you will implement the mapView:viewForAnnotation: delegate method. The map view will call this method when the map needs the view for an annotation. If you wanted to implement a custom view for your annotations, you would do it in this method. Instead of implementing a custom view, you will use the MKPinAnnotationView; however, you could easily replace this with your own view. You will change the color of the pin based on the user rating of the business that you are plotting on the map. Here is the code:

- (MKAnnotationView *)mapView:(MKMapView *)mapView
            viewForAnnotation:(id <MKAnnotation>)annotation
{

    // If we are displaying the user's location, return nil
    //  to use the default view
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
           return nil;
    }

    // Try to dequeue an existing pin
    MKPinAnnotationView *pinAnnotationView =
        (MKPinAnnotationView *)
        [self.mapView dequeueReusableAnnotationViewWithIdentifier:@"location"];

    if (!pinAnnotationView) {
        // We could not get a pin from the queue
        pinAnnotationView=[[[MKPinAnnotationView alloc]
                            initWithAnnotation:annotation
reuseIdentifier:@"location"] autorelease];

        pinAnnotationView.animatesDrop=TRUE;
        pinAnnotationView.canShowCallout = YES;

    }

    // We need to get the rating from the annotation object
    //  to color the pin based on rating
    Result *resultAnnotation = (Result*) annotation;

    if (resultAnnotation.rating > 4.5) {
        pinAnnotationView.pinColor = MKPinAnnotationColorGreen;
    }
    else if (resultAnnotation.rating > 3.5) {
        pinAnnotationView.pinColor = MKPinAnnotationColorPurple;
    }
    else {
        pinAnnotationView.pinColor = MKPinAnnotationColorRed;
    }


    return pinAnnotationView;

}
                                                         
Using MapKit

The first line of the method checks to see if the annotation is for the user location view. If it is, you simply return nil to tell the map to use the default annotation.

Next, you will see the attempt to dequeue an existing annotation view. The MapView works very much like the TableView in this respect. It doesn't make sense to keep invisible map annotations in memory. Therefore, the MapView creates and releases annotations as they become visible or disappear from the map respectively. Instead of creating new annotation instances every time, the MapView maintains an internal queue of annotation objects that it can reuse. Therefore, you first try to dequeue an annotation. If you cannot, you create a new pin annotation view with the correct reuse identifier. Then, you set the attributes of this view.

Next, you cast the annotation that the MapView is asking for to a Result object. Then, you use the rating property of the Result to set the color of the pin. Finally, you return the pinAnnotationView.

Finishing Up

The code is now complete. You should be able to successfully build and run the application. If you attempt to run the application in the simulator, you will see that the device thinks that it is at Apple headquarters, regardless of where you are actually located. This is by design. Enter a search term in the search bar and watch the pins drop to show you the results. You can view the XML returned by the web service in the console.

EXAMPLE 2: TERM EXTRACTION

When making calls to a web service, you will often use the HTTP GET method to send parameters to the service. When dealing with REST-based web services, you use GET to indicate that you are performing a query for some data from the server. There are occasions where you will need to POST data to the server. Many SOAP-based web services use POST to send data. REST uses the POST method to indicate that you are sending data to the server and intend to modify the database.

Sending a POST request is very similar to sending a GET request with some minor exceptions, as you will see in the example code.

In this example, you will make a call to the Yahoo! Term Extraction service. This service returns a list of what it deems to be the significant words and phrases in the text passage that you submit. There is no definition of what Yahoo! determines to be "significant," nor is their algorithm to determine significance public. Because of the length of the string that you can submit to the service, it is not practical to use the GET method; therefore, the service requires that you use POST to send your string into the web service. You can apply the same principles that you use here to any web service that requires you to submit data using the POST method. The completed example will look like Figure 11-5.

Complete term extraction application

Figure 11.5. Complete term extraction application

Getting Started

To get started, open Xcode and create a new View-based application called TermExtract. In the TermExtractViewController.h header file, add instance variables for two UITextView variables:

UITextView *textToExtractTextView;
UITextView *extractedTermsTextView;

Next, add properties for these UITextViews:

@property (nonatomic, retain) IBOutlet UITextView *textToExtractTextView;
@property (nonatomic, retain) IBOutlet UITextView *extractedTermsTextView;

Also, add instance variables for the response data that you will receive from the server in response to your request, and the characters that you will capture during XML parsing:

NSMutableData *responseData;
NSMutableString *capturedCharacters;

Now, add a property for the responseData:

@property (nonatomic, retain) NSMutableData *responseData;

Next, add an IBAction method called extractTerms that you will call after the user enters the text to send to the service. Finally, add an instance method called parseXML that you will invoke to start the XML processing:

- (IBAction) extractTerms:(id)sender;
- (void) parseXML;

The complete header should look like this:

#import <UIKit/UIKit.h>

@interface TermExtractViewController : UIViewController {
    UITextView *textToExtractTextView;
    UITextView *extractedTermsTextView;
    NSMutableData *responseData;
    NSMutableString *capturedCharacters;

}

@property (nonatomic, retain) IBOutlet UITextView *textToExtractTextView;
@property (nonatomic, retain) IBOutlet UITextView *extractedTermsTextView;
@property (nonatomic, retain) NSMutableData *responseData;

- (IBAction) extractTerms:(id)sender;
- (void) parseXML;

@end
                                                         
Getting Started

In the implementation file, synthesize the textToExtractTextView,extractedTermsTextView,responseData properties:

@synthesize textToExtractTextView,extractedTermsTextView,responseData;

Then, add the code to clean up the properties in the viewDidUnload method:

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.textToExtractTextView=nil;
    self.extractedTermsTextView=nil;
    self.responseData=nil;
}
                                                         
Getting Started

Finally, release your instance variables and call the superclass's dealloc method in dealloc:

- (void)dealloc {
    [textToExtractTextView release];
    [extractedTermsTextView release];
    [responseData release];

    [super dealloc];
}
                                                         
Getting Started

Building the User Interface

You will now build the user interface for the application using Interface Builder. Double-click on the TermExtractViewController.xib file in Xcode to open the file in Interface Builder. Once the interface view is open, add two UITextViews, two UILabels, and a UIButton, as shown in Figure 11-6.

Term extract user interface

Figure 11.6. Term extract user interface

Change the title attribute of the UIButton to read "Extract Terms." Change the text of the top UILabel to read "Text to extract:" and the bottom UILabel to "Extracted Terms:".

As default text in the "Text to extract:" TextView, I set the text to the Declaration of Independence. I have included a text file containing the declaration, or you could use your own text or just provide text at runtime. Delete the default text from the extracted terms TextView.

Next, you need to hook up the TextViews to the proper outlets in Interface Builder. Hook up the Extract Terms button to the IBAction extractTerms in File's Owner. In the TermExtractViewController.m implementation file, implement a stub extractTerms method to log when someone calls the method. You will use this to verify that you have correctly wired up the button in Interface Builder. Here is the stub code:

- (IBAction) extractTerms:(id)sender
{
    NSLog (@"extractTerms");
}

Build and run the application. Click the extractTerms button and verify that you see the log message in the console. This shows that you have correctly wired the button to the method. If you do not see the message in the console, make sure that you have properly wired the button to the message in Interface Builder.

You are finished with the user interface, so you can close Interface Builder.

Implementing the POST Call

You will implement the extractTerms method to POST the request to the web service.

The first thing that you do in this method is to dismiss the keyboard by calling the resignFirstResponder method on the TextView. Next, you clear the list of extracted terms to eliminate old results:

- (IBAction) extractTerms:(id)sender
{
    NSLog (@"extractTerms");

    // Hide the keyboard
    [self.textToExtractTextView resignFirstResponder];

    // Clear the old extracted terms
    self.extractedTermsTextView.text = @"";
                                                         
Implementing the POST Call

Now, you create a string to hold the URL that you plan to call. This is the address of the Yahoo! Term Extraction web service. Next, you create an NSURL object with this string:

// Create a string for the URL
    NSString *urlString =
        @"http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction";

    // Create the NSURL
    NSURL *url = [NSURL URLWithString:urlString];
                                                         
Implementing the POST Call

The next line is where using the POST method differs from using GET. If you recall, when using the GET method, you simply set the URL string, set the parameter values inline, and sent the request off through the NSURLConnection. When using the POST method, you need to do things a little differently. After you create the NSURLRequest, you will need to modify some of its properties. Therefore, you must use an NSMutableURLRequest instead:

// Create a mutable request because we will append data to it.
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
        cachePolicy:NSURLRequestUseProtocolCachePolicy
        timeoutInterval: 30.0];
                                                         
Implementing the POST Call

The first change that you will make to the request is to set the HTTP method that you plan to use. Remember that you are using the POST method. The default method is GET, so you have to change this in the request to POST using the setHTTPMethod method:

// Set the HTTP method of the request to POST
    [request setHTTPMethod:@"POST"];
                                                         
Implementing the POST Call

Next, you will build a string to hold your parameters. In this example, there is only one parameter, but many parameters can optionally be passed using the POST method. You should note that parameters must be passed using the HTML parameter passing syntax name=value just like when using the GET method. In your implementation, make sure that you replace the appid with the actual appid that you receive from Yahoo! after you register your application. Here is your parameter string:

// Build a string for the parameters
    NSString *parameters = [[NSString alloc] initWithFormat:
        @"appid=YOUR_ID_GOES_HERE&context=%@",
        self.textToExtractTextView.text];
                                                         
Implementing the POST Call

When you use the GET method to call a web service, you pass the parameters in the query string of the HTTP request. However, when you use POST, you pass those parameters in the body of the HTTP message. Therefore, you have to set the HTTP body using the setHTTPBody method:

// Set the body of the request
    [request setHTTPBody:[parameters dataUsingEncoding:NSUTF8StringEncoding]];
                                                         
Implementing the POST Call

The rest of the code for the method is the same as you have seen before. First, you create the NSURLConnection:

NSURLConnection *connection =
    [[NSURLConnection alloc] initWithRequest:request delegate:self];
                                                         
Implementing the POST Call

Next, you instantiate your responseData property:

// Make sure that the connection is good
    if (connection) {
        // Instantiate the responseData data structure to store to response
        self.responseData = [NSMutableData data];

    }
else {
        NSLog (@"The connection failed");
    }
                                                         
Implementing the POST Call

Finally, you need to clean up your local variables:

// Clean up our local variables
    [urlString release];
    [parameters release];
                                                         
Implementing the POST Call

Here is the complete method implementation:

- (IBAction) extractTerms:(id)sender
{
    NSLog (@"extractTerms");

    // Hide the keyboard
    [self.textToExtractTextView resignFirstResponder];

    // Clear the old extracted terms
    self.extractedTermsTextView.text = @"";

    // Create a string for the URL
    NSString *urlString =
        @"http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction";

    // Create the NSURL
    NSURL *url = [NSURL URLWithString:urlString];


    // Create a mutable request because we will append data to it.
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
        cachePolicy:NSURLRequestUseProtocolCachePolicy
        timeoutInterval: 30.0];


    // Set the HTTP method of the request to POST
    [request setHTTPMethod:@"POST"];

    // Build a string for the parameters
    NSString *parameters = [[NSString alloc] initWithFormat:
        @"appid=YOUR_ID_GOES_HERE&context=%@",
        self.textToExtractTextView.text];

    // Set the body of the request
    [request setHTTPBody:[parameters dataUsingEncoding:NSUTF8StringEncoding]];

    // Create the connection and send the request
NSURLConnection *connection =
    [[NSURLConnection alloc] initWithRequest:request delegate:self];

    // Make sure that the connection is good
    if (connection) {
        // Instantiate the responseData data structure to store to response
        self.responseData = [NSMutableData data];

    }
    else {
        NSLog (@"The connection failed");
    }

    // Clean up our local variables
    [urlString release];
    [parameters release];
}
                                                         
Implementing the POST Call

Receiving the XML Response

In order to receive the response from the web service, you need to implement the NSURLConnection delegate methods as you did in the previous example.

First, you will implement the connection:didReceiveResponse: method. This delegate method is called when the NSURLConnection creates the response. The connection could call this method multiple times, so you need to reset your response data by setting its length to zero each time this method runs. Here is the implementation:

// Called when the connection has enough data to create an NSURLResponse
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
    NSLog (@"connection:didReceiveResponse:");
    NSLog(@"expectedContentLength: %qi", [response expectedContentLength] );
    NSLog(@"textEncodingName: %@", [response textEncodingName]);

    [self.responseData setLength:0];

}
                                                         
Receiving the XML Response

Next, you need to implement the connection:didReceiveData: delegate method. The connection calls this method each time it receives data so you need to append the received data to your responseData buffer:

// Called each time the connection receives a chunk of data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog (@"connection:didReceiveData:");

    // Append the received data to our responseData property
    [self.responseData appendData:data];

}
                                                         
Receiving the XML Response

Now you need to implement connectionDidFinishLoading. The delegate calls this method when the connection has completed loading the requested data. In this method, you convert the response data to a string, clean up the connection, and call the method to parse the XML:

// Called when the connection has successfully received the complete response
- (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: 
%@", responseString);

    [responseString release];
    [connection release];

    [self parseXML];
}
                                                         
Receiving the XML Response

Finally, you should implement the connection:didFailWithError: method. Here you will log that an error occurred. In a production application, you would want to provide more robust error handling. Here is the implementation:

// Called when an error occurs in loading the response
- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error
{
    NSLog (@"connection:didFailWithError:");
    NSLog (@"%@",[error localizedDescription]);

    [connection release];

}
                                                         
Receiving the XML Response

Parsing the Response XML

After you submit your request, you will receive an XML response from the web service. The response will contain the most relevant words and phrases from the text that you sent into the service, in order of relevance. The response that I received when I sent the declaration to the web service looked like this:

<?xml version="1.0"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="urn:yahoo:cate" xsi:schemaLocation="urn:yahoo:cate
 http://search.yahooapis.com/ContentAnalysisService/V1/
 TermExtractionResponse.xsd">
    <Result>life liberty and the pursuit of happiness</Result>
    <Result>powers of the earth</Result>
    <Result>liberty and the pursuit of happiness</Result>
    <Result>pursuit of happiness</Result>
    <Result>certain unalienable rights</Result>
    <Result>absolute despotism</Result>
    <Result>political bands</Result>
    <Result>transient causes</Result>
    <Result>decent respect</Result>
    <Result>long train</Result>
    <Result>direct object</Result>
    <Result>usurpations</Result>
    <Result>sufferance</Result>
    <Result>laws of nature</Result>
    <Result>one people</Result>
    <Result>form of government</Result>
    <Result>when in the course of human events</Result>
    <Result>evils</Result>
    <Result>prudence</Result>
    <Result>mankind</Result>
</ResultSet>

You need to implement the parseXML function, just as you did in the previous example, to parse this response XML:

- (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");
    }
// Release the parser because we are done with it
    [xmlParser release];
}
                                                         
Parsing the Response XML

In this method, you first 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.

Finally, you will implement your NSXMLParser delegate methods:

// Called when the parser encounters a start element
- (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
  qualifiedName:(NSString *)qualifiedName
     attributes:(NSDictionary *)attributeDict {

    // Check to see which element we have found
    if ([elementName isEqualToString:@"Result"]) {
        // Initialize the capturedCharacters instance variable
        capturedCharacters = [[NSMutableString alloc] initWithCapacity:100];
    }
}

// Called when the parser encounters an end element
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

    NSLog (@"didEndElement");

    // Check to see which element we have ended

    // We ended a Result element
    if ([elementName isEqualToString:@"Result"]) {
        NSLog (@"capturedCharacters: %@", capturedCharacters);

        self.extractedTermsTextView.text = [self.extractedTermsTextView.text
            stringByAppendingFormat:@"%@
",capturedCharacters];

        // Release the capturedCharacters instance variable
        [capturedCharacters release];
        capturedCharacters = nil;
    }



}

// Called when the parser finds characters contained within an element
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (capturedCharacters != nil) {
        [capturedCharacters appendString:string];
    }
}
                                                         
Parsing the Response XML

Because you are only interested in Result elements, this code is straightforward. If you encounter the start of a Result element, you initialize your capturedCharacters instance variable in the didStartElement method. In didEndElement, you check to see that you ended a Result element. Then, you append the capturedCharacters string to the extractedTermsTextView.

Finishing Up

The application is now complete. You should be able to successfully build and run the program. When you tap the Extract Terms button, you will send the query to the web service. If you have an active Internet connection, and you have properly configured your own appid, you should receive an XML result set back that contains the extracted terms. You can verify this in the console log. The code will take the text contained in each Result element, parse it, and append it to the extractedTermsTextView in the user interface. Feel free to paste in any block of text that you find interesting to see what the Yahoo! service feels are the most significant words or phrases in the document.

MOVING FORWARD

In this chapter, you learned about the basics of XML web services. Then you learned how to call XML web services using both the HTTP GET and POST methods. This will enable you to call any web service available on the Internet.

You also learned how to use the Core Location framework to access the GPS functionality and determine a device's location. Then, you learned how to use the MapKit framework to display and annotate maps.

Over the course of this entire book, you have explored the full spectrum of dealing with data on the iPhone and iPad. You learned how to display data on the device, extract data from enterprise systems and store it on the device, use Core Data to generate and manage data on the device, and use web services to communicate from your application to other services.

You now have all of the tools necessary to build robust, data-driven applications. I hope that you find the exploration of the frameworks and functionality available in the iPhone SDK helpful in your daily work. I hope that you take this knowledge, go out, and build amazing applications, because the iPhone and iPad are amazing platforms for your software. We are still only at the beginning for these devices. As these technologies evolve, the capabilities of the devices will only get better, allowing you to build applications that are even more amazing!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset