This chapter covers |
|
We started this book with a look at the web. Chapters 3 through 8 offered an extensive discussion of building iPhone web apps using HTML, CSS, JavaScript, and the dynamic programming language of your choice. As we said at the time, web development is one of two major ways that you can program for the iPhone, the other being the SDK that we’ve spent the last ten chapters on.
We’ve generally suggested web apps as the proper platform for creating internet-related programs. This chapter will present some solutions for when that’s not the case. Even if you’re depending heavily on the web, there are numerous reasons that you might want to program using the SDK. You might want to make use of its more extensive graphic capabilities. You could be designing something of sufficient complexity that you want to use a well-organized object-oriented environment. You might want to monetize your app without having to depend on ads. For whatever reason, you’ve decided to design an SDK web app, not an HTML-based web app, and now you need to know how to do so.
In this chapter, we’re going to cover the major ways to access the internet from the SDK. You can do so in a variety of ways, and we’ll outline their hierarchy in our first section.
internet programming involves a hierarchy of protocols. At the lowest level, you have the sockets that you use to connect from one computer to another. Above them are a variety of more sophisticated technologies, such as FTP, Bonjour, and HTML. HTML is a critical protocol, represented on the iPhone by both low-level access and the high-level UIWebView. Recently an increasing number of protocols have been built on top of HTML, forming what we call the social network.
This hierarchy of internet protocols is shown in figure 20.1, along with iPhone OS classes of note.
In this chapter, we’re going to cover all of these protocols, starting with the lowest level, but our real focus will be on the higher-level internet and social network protocols, because they’re the protocols that are best supported by the iPhone, and they’re the ones you’re most likely to want to interact with.
We’ve opted not to pay much attention to BSD sockets and the lower-level networking classes, because we expect they’ll be of little interest to most iPhone programmers. If you need to work with BSD sockets, you should look at Apple’s “Introduction to CFNet-work Programming Guide.”
If you need to work with the lower-level protocols, CFNetwork provides a variety of classes that you’ll find useful. You can find more information on them in the “Networking & internet” topic in the Apple docs. In particular, the “CFNetwork Framework Reference” will give you an overview of the various classes. Among the classes are CFFTPStream, which lets you communicate with FTP servers, and CFNetServices, which gives you access to Bonjour—Apple’s service discovery protocol. There are also two low-level HTTP-related classes, CFHTTPMessage and CFHTTPStream. We’re going to leave these classes alone, as our HTML work will be related to the higher-level NSURL, NSURLRequest, UIWebView, NSMutableURLRequest, and NSURLConnection classes.
Rather than skipping over these low-level and unabstracted protocols entirely, we’ll take a look at one of them, CFHost. It’s the easiest to work with and perhaps the most immediately useful.
CFHost allows your program to request information about an internet host, such as its name, its address, and whether it’s reachable. Listing 20.1 shows a sample of how to determine whether a host name exists or not.
Our sample method, reportStatus:, is activated by a button push. It reads a host name from a UITextField called myInput and reports out to a UITextView called myOutput.
All uses of the CFHost commands follow the same pattern. First you create a CFHostRef object with CFHostCreateCopy, CFHostCreateWithAddress, or CFHostCreateWithName . Then you use CFHostStartInfoResolution to request a certain type of information, which can be kCFHostAddresses, kCFHostNames, or kCFHost-Reachability . This example omits a final step where you retrieve your information with CFHostGetAddressing, CFHostGetNames, or CFHostReachability—something that wasn’t necessary here because the point was to see if the request for an address resolved correctly at all.
You can find more information on these functions, and on how to use a callback function to make the host resolution asynchronous, in the CFHost reference.
We consider this look at low-level networking—and CFHost—an aside, meant only to hint at what is possible if you must do lower-level networking work. Now we’ll move on to higher-level HTML-related network work that’s more likely to be the focus of your iPhone network programming. The first thing you’ll need to know is how to use the iPhone’s URL objects.
With HTTP being the basis of most iPhone internet programming, it shouldn’t be a surprise that URLs are a foundational technique for internet-based programming. You’ll use them whether you’re calling up UIImageViews, accessing content by hand, or parsing XML. As a result, we’re going to spend a bit of time on the two fundamental URL classes: NSURL and NSURLRequest.
An NSURL is an object that contains a URL. It can reference a web site or a local file, like any URL can. You’ve used it in the past to access Apple’s stock page and to load up local media files for play.
NSURL and CFURLRef
NSURL is a toll-free bridge to CFURL, making an NSURL * and a CFURLRef equivalent. We took advantage of this in chapter 18 when dealing with the MPMoviePlayerController and with sounds. Whenever you need to create a CFURLRef, you can do so using the standard methods for NSURL creation that are described in this chapter.
As noted in the NSURL class reference, there are numerous methods that you can use to create an NSURL. The most important ones are listed in table 20.1.
Method |
Summary |
---|---|
fileURLWithPath: |
Creates a URL from a local file path |
URLWithString: |
Creates a URL from a string; equivalent to initWithString: |
URLWithString:relativeToURL: |
Adds a string to a base URL; equivalent to initWithString:relativeToURL: |
Once you’ve got an NSURL in hand, you can do any number of things with it:
The first two possibilities require only the use of an NSURL, but when you’re working with a UIWebView, you must first create an NSURL and then turn it into an NSURLRequest.
The NSURLRequest class contains two parts: a URL and a specific policy for dealing with cached responses. As noted in table 20.2, there are four ways to create an NSURL-Request, though we expect that you’ll usually fall back on the simple factory method, requestWithURL:.
Method |
Summary |
---|---|
requestWithURL: |
Creates a default request from the URL; equivalent to initWithURL: |
requestWithURL:cachePolicy:timeoutInterval: |
Creates a request with specific caching choices; equivalent to initWithURL: cachePolicy:timeoutInterval: |
By default, an NSURLRequest is built with a caching policy that’s dependent upon the protocol, and a timeout value of 60 seconds, which should be sufficient for most of your programming needs. If you need to get more specific about how things are loaded, you can call requestWithURL:cachePolicy:timeoutInterval:, giving it an NSURLRequestCachePolicy for the policy and an NSTimeInterval for the timeout.
You can also create a more interactive NSURLRequest by using the NSMutableURL-Request class, which allows you to more carefully form and modify the request that you’re sending. We’ll talk about this in section 20.6, when we examine how to send POST requests from an iPhone.
The NSURLRequest will get you through most web page work. As with the NSURL, there are a few different things that you can do with an NSURLRequest. You can hand it off to a UIImageView, or you can use it to read in the contents of a web page, to later manipulate it by hand.
To read the contents of a web page manually, you need to access an NSURLRequest’s properties. Table 20.3 lists some of the most important ones, though, as usual, more information can be found in the class reference.
Summary |
|
---|---|
allHTTPHeaderFields |
Returns an NSDictionary of the header |
HTTPBody |
Returns an NSData with the body |
valueforHTTPHeaderField: |
Returns an NSString with the header |
The catch with these properties is that you can only work with well-defined HTML pages. Most notably, the NSURLRequest properties can’t read fragments, such as would be generated by Ajax or JSON, nor can they parse other sorts of content, such as XML or RSS.
Other ways to read HTTP content
If you’re not reading data that meets the HTML protocol, you can’t use NSURLRe-quest’s properties to access the data. Instead, you must fall back on other functions that let you read in data from an NSURL.
We’re already met functions that read data that follows other protocol specifications, such as the MPMoviePlayerController and the sound players from chapter 18. Similarly, in this chapter we’ll talk about an XML parser. All of these classes can read directly from a URL.
If you need to capture raw data that isn’t set in HTML, the best way to do so is with an init or factory method that reads from a URL, such as NSData’s dataWithContentsOfURL:. We’ll look at an example of that in the last section of this chapter.
You may also discover that you need a more interactive way to deal with HTML data. In this case, you’ll probably use an NSURLConnection object; but as with the NSMutableURLRequest, we’re going to save that for later, because you’ll typically only need to use it when you’re POSTing information to a web page rather than just retrieving it.
For the moment, we’re going to put all of these complexities aside and instead look at how to display straight HTML data using the SDK’s UIWebView.
One of the easiest ways to connect up to the internet is to use the UIWebView class, which gives you full access to web pages of any sort. In some ways, this class is of limited utility, because it largely duplicates Safari, and Apple isn’t interested in approving applications that just duplicate their existing technology. But there are clearly situations where you’ll want a program to be able to refer to some specific web pages, and that’s what UIWebView is for.
The class is easy to use—we included it in simple examples way back in chapters 11 and 12. The only real complexity is in building an NSURL or NSURLRequest object to get your web view started, but that process follows the methods we’ve already seen.
There are two main ways to fill a web view once you’ve created it, as listed in table 20.4. Most frequently, you’ll start with an NSURLRequest, which you must have created using the two-step process that we described in the previous section, but you can also load a web view with an NSURL and an NSString. A few other init methods can be found in the class reference.
Method |
Summary |
---|---|
loadHTMLString:baseURL: |
Loads a page from a URL and a string |
loadRequest: |
Loads a page from an NSURLRequest |
Assuming you use the more common NSURLRequest method, you can put all the lessons you’ve learned so far together, which is just what you did back in chapter 11 when you created your first UIWebView:
[myWebView loadRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:url]]];
Once you’ve got a UIWebView, you can start working with it. There are five UIWebView methods and properties of particular note, which are summarized in table 20.5.
Method/Property |
Type |
Summary |
---|---|---|
detectsPhoneNumbers |
Property |
Boolean that determines whether phone numbers become links |
goBack |
Method |
Moves back a page; check canGoBack property first |
goForward |
Method |
Moves forward a page; check canGoForward property first |
reload |
Method |
Reloads the current page |
scalesPageToFit |
Property |
Boolean that determines whether the page is zoomed into a viewport and whether user zooming is allowed |
The most exciting options in our mind are the goBack, goForward, and reload methods, which can give you some control over how the UIWebView moves among pages. Similarly, the loadRequest: method can be continually rerun if you want to move a user through multiple pages, treating the UIWebView more like a web slideshow than a browser.
In our opinion, the scalesPageToFit property does not work correctly at the current time. It always scales the page as if the UIWebView were full screen, and it leaves a less than optimal view if you create a small UIWebView, as we will do in our next example. We expect this to be resolved in a future version of the SDK.
As we wrote in chapter 12, the biggest gotcha in using a UIWebView is that you can’t load a URL straight from Interface Builder. Expect to always use the NSURL-NSURLRequestloadRequest: process that we’ve laid out here to load up pages into your web views.
There’s one other element that we didn’t discuss when we talked about web views in chapters 11 and 12: you can set a delegate to manage a few common responses. You must follow the UIWebViewDelegate protocol, which lists four methods, described in table 20.6.
Method |
Summary |
---|---|
webView:shouldStartLoadWithRequest:navigationType: |
Called prior to content loading |
webViewDidStartLoad: |
Called after content begins loading |
webViewDidFinishLoad: |
Called after content finishes loading |
webView:didFailLoadWithError: |
Called after content fails to load |
Together with the UIWebView methods, these delegate methods give you considerable power. You could use them to load alternative web pages if the preferred ones don’t load. Or, continuing our slideshow analogy, you could use them to continuously load new pages when old ones finish. All those possibilities highlight the ways that you might be able to use the UIWebView as more than just a Safari clone.
As we’ve previously stated, UIWebViews are pretty easy to set up, and we’re not going to spend a lot of time on a coding sample. Listing 20.2 presents a simple example that creates a set of web page thumbnails, similar to the startup page of the Google Chrome browser. It uses delegates first to get rid of UIWebViews that don’t load, and later to zoom in on the one the user selects.
It should be initially created in Interface Builder by laying out four UIWebViews. Make sure that they’re set to scale, and set their delegates to be the view controller.
To start with, you read a set of (exactly) four URLs from a file and use the NSString method componentsSeparatedByString: to turn them into an NSArray that you use to seed your web views . After that, it’s a question of responding to delegation messages.
The webView:didFailLoadWithError: method shows off some valuable techniques for both debugging and error management. NSLog is what you want to use when you want to do a printf-style reporting of runtime variables. It’ll output to /var/log/system.log when you run it inside the iPhone Simulator.
Within a UIWebView, there are two error codes that will come up with some frequency: -1003 is “Can’t find host” and -999 is “Operation could not be completed.” This example ignores -999 (which usually means that the user clicked a link before the page finished loading), but in the case of a -1003 failure, you dismiss the web view.
Finally, the webViewDidFinishLoad: method zooms in on an individual web view (dismissing the rest) once a user clicks on a link. Realistically, this should occur whenever the user touches the web view, but we wanted to show the UIWebView delegate methods, so we chose this slightly more circuitous route.
And that’s it—a simple web thumbnail program, as shown in figure 20.2. It could be improved by giving the user the ability to manage the selected URLs and by polishing up the way the user selects an individual page (including an option to return to the thumbnail page afterward). For our purposes, though, it does a great job of demonstrating some of the intricacies of the UIWebView.
Debugging
We haven’t talked about debugging your SDK program much in this book, primarily for reasons of space. Here’s a short overview of our favorite techniques:
Xcode itself provides the best debugging. Pay careful attention to autocompletion of words and note when an expected autocompletion doesn’t occur, because that usually means you didn’t set a variable correctly.
The warnings and errors that appear on compilation should always be carefully considered.
During Simulator runtime, we suggest keeping an eye on /var/log/system.log on your Mac. You can do this by opening a terminal and running tail -f /var/log/system.log. You can log to the system log by hand with NSLog. If a program crashes at runtime, you’ll usually see an error here, and then you can go to Xcode to step back through a trace to see exactly where the crash occurred.
Finally, once you’re done with your program you should run it through Instruments to check for memory leaks.
For more information, take a look at the “Xcode Debugging Guide,” “Debugging with GDB,” and the “Instruments User Guide,” Apple articles which contain comprehensive explanations of those subjects.
Before we finish with web views entirely, we’re going to look at one more example. Back in chapter 17, we talked about how Core Location would be better served once we got into the world of the internet. We’re going to look at the first of two Core Location internet examples.
Google Maps should be an ideal way to show off Core Location, because it’s already built into the iPhone and because it can take longitude and latitude as URL arguments. Unfortunately, the reality falls a little short of that ideal at the time of this writing.
There are two problems. Most notably, as a programmer, you have no access to the proprietary Google Maps interface built into the iPhone. Instead, you have to use a UIWebView to show the map results (which is why we’re covering this example in this section). That leads to the second problem, which is that Google Maps often sizes itself in weird ways when displayed in a UIWebView. This is probably due to some combination of UIWebView always assuming that it will appear full screen—which we’ve already discussed—and Google Maps trying to do the right thing when it detects that you’re on the iPhone. We expect Google Maps’ presentation will slowly improve through future releases of the SDK.
Listing 20.3 shows a simple example of how to call up Google Maps in a web view called myWeb using a Location Manager–derived latitude and longitude. It additionally does a search for a type of business listed in a UITextField called myEntry.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
if (myEntry.text) {
[myLM stopUpdatingLocation];
[myActivity stopAnimating];
NSMutableString *googleSearch = [NSMutableString
stringWithFormat:@"http://maps.google.com?q=%@&sll=%f,%f",
myEntry.text,newLocation.coordinate.latitude,
newLocation.coordinate.longitude];
[googleSearch replaceOccurrencesOfString:@" " withString:@"+"
options:NSCaseInsensitiveSearch range:NSMakeRange(0,
[googleSearch length])];
[myWeb loadRequest:[NSURLRequest requestWithURL:[NSURL
URLWithString:googleSearch]]];
}
}
The functionality here is quite simple. All the hard work is done in the middle two lines, which set up the googleSearch string. When you pass Google Maps a q argument, you’re sending it a word to search on, and when you give it an sll argument, you’re defining a latitude and longitude. As a result, when handed this search string, Google will put together a map that shows businesses of type q near the designated coordinates. The following line of code then does some minimal work to urlencode the query.
This sample code is sufficient if you’re playing around with a program for your own use, and more importantly it shows how easy it is to mix Core Location and a UIWebView, but what would you do if you wanted to make a production-ready app that supported Google Maps? The answer is, you’d do a fair amount more work—more than we can cover here in anything but an outline.
In order to create a top-grade Google Maps application, you’d need to fall back on one of the lessons we covered way back in chapter 2: you must create an integrated client-server program that uses both web development and the SDK. Here’s how it would work:
We don’t think this sort of work will be necessary forever, but, for now, it’s a powerful example of exactly why you might want to create a hybridized program using both iPhone web development and the SDK.
There’s a Google Code project that presumes this sort of setup and includes a sophisticated iPhone-side interface for viewing the map. It’s called iPhone Google Maps and can currently be found here:
http://code.google.com/p/iphone-google-maps-component
Having now covered web views in some depth, we’re ready to begin looking at higher-level protocols, starting with the one best supported by the iPhone: XML.
XML (Extensible Markup Language) is a generalized markup language whose main purpose is to deliver data in a well-formed and organized way. It has some similarities to HTML, and an XML version of HTML has been released, called XHTML.
Because of XML’s popularity on the internet, the iPhone SDK includes its own XML parser, the NSXMLParser class. This is an event-driven API, which means that it will report start and end tags as it moves through the document, and you must take appropriate action as it does.
XML and files
When using NSXMLParser, you’ll probably immediately think about reading data taken from the internet, but it’s equally easy to read XML from your local files. You create a path to the file, and then use NSURL’s fileURLWithPath: method, as we’ve seen elsewhere in this book.
An XML file can be a nice intermediary step between saving data in plain text files and saving it in databases, which were two of the options we saw in chapter 16. Although you’re still using files, you can do so in a well-organized manner. We’ll see an example of this in section 20.5.3.
Running the NSXMLParser involves setting it up, starting it running, and then reacting to the results. We’ll cover that process in the rest of this section. For more information on any of these topics, we suggest reading Apple’s “Event-Driven XML Programming Guide for Cocoa,” but we’ll provide a tutorial on the basics, starting with the parser class.
In order to get started with the NSXMLParser, you need to create it, set various properties, and then start it running. The most important methods for doing so are listed in table 20.7.
Method |
Summary |
---|---|
initWithContentsOfURL: |
Creates a parser from an NSURL |
initWithData: |
Creates a parser from an NSData |
setDelegate: |
Defines a delegate for the parser |
parse |
Starts the parser going |
Not listed are a few additional setters that allow the parser to process namespaces, report namespace prefixes, and resolve external entities. By default, these properties are all set to NO; you shouldn’t need them for simple XML parsing.
There are approximately 20 delegate methods for NSXMLParser. They are all optional: you only need to write delegates for things that you’re watching for. We’re not going to cover the delegate methods for mapping prefixes, comments, external entities, and many other things of somewhat less importance.
Instead, we’re going to look at the five most critical methods that you’ll need to use whenever you’re parsing XML. These are the methods that report the start and end of elements, the contents inside, when the XML parsing has ended (unrecover-ably!) with an error, and when the XML parsing has ended because it’s all done. These are listed in table 20.8.
Method |
Summary |
---|---|
parser:didStartElement:namespaceURI: qualifiedName:attributes: |
Reports the start of an element and its attributes |
parser:foundCharacters: |
Reports some or all of the content of an element |
parser:didEndElement:namespaceURI: qualifiedName: |
Reports the end tag of an element |
parserDidEndDocument: |
Reports the end of parsing |
parser:parseErrorOccurred: |
Reports an unrecoverable parsing error |
Generally, when you’re parsing XML, you should take the following steps as you move through elements:
1.
When you receive the didStartElement: method, look at the NSString to see what element is being reported, and then prepare a permanent variable to save its content, to prepare your program to receive the information, or both. Optionally, look at the NSDictionary passed by the attributes: handle and modify things accordingly.
2.
When you receive the foundCharacters: method, save the contents of the element into a temporary variable. You may have to do this several times, appending the results to your temporary variable each time, because there’s no guarantee that all of the characters will appear in one lot.
3.
When you receive the didEndElement: method, copy your temporary variable into your permanent variable, take an action based upon having received the complete element, or both.
4.
Optionally, when you receive parserDidEndDocument:, do any final cleanup.
Beyond that, the parser:parseErrorOccurred: method should call up an NSAlert or otherwise alert the user to the problem. As we noted, this is only for an unrecoverable problem: the user won’t be able to do anything about it without modifying the original XML itself.
In order to show how the NSXMLParser can be used, our next example involves writing a simple RSS reader. Building an RSS reader on your own will allow you to walk through the basic functionality of NSXMLParser using an XML type that’s widely available on the internet for testing.
Now that you understand the basics of XML, you’re ready to put together a sample program that uses NSXMLParser in two ways: first to read a text file and then to read an RSS feed. The results will be output to a hierarchy of tables. The first level of the hierarchy will show all the possible feeds, and the second level will show the contents of individual feeds. An example of the latter sort of page is shown in figure 20.3.
To start this project, you’ll need to create a Navigation-Based Application, which will provide you with the navigator and initial table setup needed to get this project started. In a more advanced program, you’d give users the opportunity to create a settings file for what RSS feeds they want to read, but for the purposes of this example, create an XML settings file called rssfeeds.xml by hand, using the following format:
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<feed title="RPGnet News" url="http://www.rpg.net/index.xml" />
<feed title="RPGnet Columns" url="http://www.rpg.net/columns/index.xml" />
</rdf:RDF>
For each entry, create a singular <feed> element and include title and url attributes.
Once you’ve added rssfeeds.xml to your project, you’re ready to write the code for the top-level table, which will parse your local XML file and give your user the option to select one of the RSS feeds. Listing 20.4 displays this code, which appears in the main view controller.
This example begins by reading in XML from a file . The result is a lot more pleasing than trying to read raw text, as in the thumbnail example earlier in this chapter, so we’ll suggest encoding simple preference files as XML in the future.
Because we designed a simple XML format, where the information is encoded as attributes, you only have to watch one delegate method, didStartElement: . Here you add the information to rssList, an NSMutableArray, for use later. The only other thing you have to do with your XML parser is clean it up when you’re done .
The next few functions are standard table view work, as you define the sections , rows , and cells using the rssList array you created. Finally, you define what happens when the user selects a row , and that is to call up a brand new type of object, the rssViewController.
The rssViewController is a subclass of the UITableViewController that will display an RSS feed if initialized with a URL. Listing 20.5 shows the complete contents, much of which is similar to listing 20.4. The biggest differences are in the XML parsing, because an RSS feed is a much more complicated XML format, even when you’re only using minimal information from it, as is the case here.
The difference in this new table view starts with the fact that you’ve got a custom init function that allows you to start an XML parser running on an RSS feed . In a more polished application, you’d check for the feed’s existence, but for this example you can dive right in.
Because this XML file is more complex than the previous one, you can’t do all your work in didStartElement: . Instead, you use this method as part of a systemic examination of the XML content, by preparing variables, creating a dictionary to hold the contents of a complete RSS item, and initializing a string to hold each individual element.
In foundCharacters: , you have to keep appending data to the current element’s string, as we promised. The XML parser will break the data from an individual element into multiple strings, so you have to be careful about this.
When you’re done , you can add your string to the element’s dictionary, and when the element is done, you can add the dictionary to the array of RSS contents that you’re maintaining.
From here on, most of the table work is pretty similar to the previous example. You read back through your master array to fill in the contents of the table. The only thing of note comes in the last method , when a user clicks on a table row. At this point, you call up a UIWebView so that the user can hop straight to the RSS feed item he is interested in.
Before we finish with XML entirely, we want to look at one more Core Location example, using GeoNames to read in altitude, as we promised we would in chapter 17.
GeoNames, which can be found at geonames.org, offers a variety of web services related to location. It can give you information on postal codes, countries, addresses, and more. A complete listing of their web services can be found at http://www.geonames.org/export/ws-overview.html.
Most of GeoNames’ information is returned either in XML or JSON format, as you prefer. We’re going to look at their XML interface here. Table 20.9 shows off some of the XML-based GeoNames information that you may find particularly useful.
Information |
Summary |
---|---|
findNearestIntersection |
Returns nearest street intersection in the US |
gtopo30 |
Returns altitude of location or -9999 for sea |
srtm3 |
Returns altitude of location or -32768 for sea |
timezone |
Returns not only time zone info, but also the current time |
We’re going to use gtopo30 to follow through on our promise from chapter 17 to look up the altitude from GeoNames based upon the Location Manager’s results. This project requires a somewhat complex chaining together of multiple delegate-driving classes, as shown in figure 20.4.
The bare skeleton of the code needed to make this work is shown in listing 20.6.
In general, this is a pretty simple application of lessons you’ve already learned. It’s also an interesting application of the internet to Core Location.
The only thing particularly innovative comes in the Core Location delegate , where you create a GeoNames URL using the format documented at the GeoNames site. Then you watch the start tags , content , and end tags , and use those to derive altitude the same way that you pulled out XML information when you were reading RSS feeds.
As we mentioned in chapter 17, the result should be an altitude that’s much more reliable than what the iPhone can currently provide, unless you’re in a tall building, in an airplane, or hang gliding.
To date, all of our examples of web parsing have involved simple GET connections, where you can encode arguments as part of a URL. That won’t always be the case on the web, and before we leave web pages behind, we’re going to return to some basics of URL requests and look at how to POST information to a web page when it becomes necessary.
Many web pages will allow you to GET or POST information interchangeably. But there will also be situations when that is not the case, and you’re instead forced to POST (and then to read back the response manually). In this section, we’re going to look at both how to program a simple POST, and how to do something more complex, like a form.
When you need to POST to the web, you’ll need to fall back on some HTML-related low-level commands that we haven’t yet discussed in depth, including NSMutableURL-Request (which allows you to build a piecemeal request) and NSURLConnection (which allows you to extract information from the web).
In general, you’ll follow this process:
For a simple synchronous response, listing 20.7 shows how to put these elements together.
NSURL *myURL = [NSURL URLWithString:@"http://www.example.com"];
NSMutableURLRequest *myRequest = [NSMutableURLRequest
requestWithURL:myURL];
[myRequest setValue:@"text/xml" forHTTPHeaderField:@"Content-type"];
[myRequest setHTTPMethod:@"POST"];
[myRequest setHTTPBody:myData];
NSURLResponse *response;
NSError *error;
NSData *myReturn = [NSURLConnection sendSynchronousRequest:myRequest
returningResponse:&response error:&error];
A large number of steps are required to move from the URL through to the data acquisition, just as there were when creating a URL for a simple UIWebView, but once you have them down, the process is pretty easy. The hardest part, as it turns out, is often getting the data ready to POST.
This code will work fine for posting plain data to a web page. For example, you could use it with the Google Spell API found at http://www.google.com/tbproxy/spell to send XML data and then read the results with NSXMLParser.
Things can get pretty tricky if you’re doing more intricate work than that, such as POSTing form data.
Sending form data to a web page follows the same process as any other POSTed data, and reading the results works the same way. The only tricky element is packaging up the form data so that it’s ready to use.
The easiest way to work with form data is to create it using an NSDictionary or NSMutableDictionary of keys and values, because that matches the underlying structure of HTML forms. When you’re ready to process the data, you pass the dictionary to a method that turns it into NSData, which can be sent as an NSMutableURLRequest body. Once you’ve written this method the first time, you can use it again and again.
Listing 20.8 shows how to turn a dictionary of NSStrings into NSData.
- (NSData*)createFormData:(NSDictionary*)myDictionary
withBoundary:(NSString *)myBounds {
NSMutableData *myReturn = [[NSMutableData alloc] initWithCapacity:10];
NSArray *formKeys = [dict allKeys];
for (int i = 0; i < [formKeys count]; i++) {
[myReturn appendData:
[[NSString stringWithFormat:@"--%@ ",myBounds]
dataUsingEncoding:NSASCIIStringEncoding]];
[myReturn appendData:
[[NSString stringWithFormat:
@"Content-Disposition: form-data; name="%@" %@ ",
[formKeys objectAtIndex:i],
[myDictionary valueForKey:[formKeys objectAtIndex: i]]]
dataUsingEncoding:NSASCIIStringEncoding]];
}
[myReturn appendData:
[[NSString stringWithFormat:@"--%@-- ", myBounds]
dataUsingEncoding:NSASCIIStringEncoding]];
return myReturn;
}
There’s nothing particularly notable here. If you have a sufficiently good understanding of the HTML protocol, you can easily dump the dictionary elements into an NSData object. The middle appendData: method is the most important one, because it adds both the key (saved in an NSArray) and the value (available in the original NSDictionary) to the HTML body.
Back outside the method, you can add the data to your NSMutableURLRequest just as in listing 20.7, except the content type will look a little different:
NSMutableURLRequest *myRequest = [NSMutableURLRequest
requestWithURL:myURL];
NSString *myContent = [NSString stringWithFormat:@"multipart/form-data;
boundary=%@",myBounds]
[myRequest setValue:myContent forHTTPHeaderField:@"Content-type"];
[myRequest setHTTPMethod:@"POST"];
[myRequest setHTTPBody:myReturn];
Some other types of data processing, such as file uploads, will require somewhat different setups, and you’d do well to look at HTML documentation for the specifics, but the general methods used to POST data will remain the same.
With POSTing out of the way, we’ve now covered all of the SDK’s most important functions related to the internet. But there’s one other topic that we want to touch upon before we close this chapter—a variety of internet protocols that you can access through third-party libraries.
Since the advent of web 2.0, a new sort of internet presence has appeared. We call it the social web. This is an interconnected network of web servers that exchange information based on various well-known protocols. If you’re building internet-driven programs, you may wish to connect up to this web so that your iPhone users can become a part of it.
In order to participate in the social web, clients need to speak a number of protocols, most of them built on top of HTML. These include Ajax, JSON, RSS, SOAP, and XML. Here’s how to use each of them from your iPhone:
These libraries should all be easy to find with simple searches on the internet, but table 20.10 lists their current locations as of this writing.
Library |
Location |
---|---|
JSON Framework |
|
TouchJSON |
|
Soap Client |
|
ToxicSOAP |
|
TouchXML |
Because of its importance to the social web, we’re going to pay some additional attention to JSON, using the TouchJSON library.
For our final example, we’re going to return to Core Location one more time, because GeoNames offers a lot of JSON information. You’re going to use GeoNames to display the postal codes near a user’s current location. Figure 20.5 shows our intended result by highlighting the postal codes near Apple headquarters, the location reported by the iPhone Simulator.
In order to get to this point, you must first install this third-party library and make use of it.
To integrate TouchJSON into your project, you must download the package from Google and move the source code into your project. The easiest way to do this is to open the TouchJSON download inside Xcode and copy the Source folder to your own project. Tell Xcode to copy all the files into your project as well. Afterward, you’ll probably want to rename the copied folder from Source to TouchJSON.
Then you need to include the header CJSONDeserializer.h wherever you want to use TouchJSON.
In order to use TouchJSON, you pass the CJSONDeserializer class an NSData object containing the JSON code. Listing 20.9 shows how to do so. In this example, this work occurs inside a location manager delegate. It’s part of a program similar to our earlier GeoNames example, but this time we’re looking up postal codes with a JSON return rather than altitudes with an XML return.
To access the JSON results, you first retrieve the data from a URL using the datawithContentsOfURL: method , which was one of the ways we suggested for retrieving raw data earlier in the chapter. Then you plug that NSData object into the CJSONDeserializer to generate an NSDictionary containing the JSON output.
The TouchJSON classes are much easier to use than the XML parser we met earlier in this chapter. All you need to do is read through the arrays and dictionaries that are output. The downside is that the resulting dictionary may take up a lot of memory (which is why the XML parser didn’t do things this way), so be aware of that if you’re retrieving particularly large JSON results.
Absent that concern, you should be on your way to using JSON and creating yet another link between your users’ iPhones and the whole world wide web.
“There’s more than one way to do it.”
That was the slogan of Perl, one of the first languages used to create dynamic web pages, and today you could equally use that slogan to describe the iPhone, one of the newest cutting-edge internet devices.
We opened this book by talking about the two different ways that you could write iPhone programs: using web technologies and using the SDK. That also highlighted two different ways that you could interact with the internet: either as an equal participant—a web-based member of the internet’s various peer-to-peer and client-server protocols—or as a pure iPhone client that runs its own programs and connects to the internet via its own means.
We’ve said before that each programming method has its own advantages, and we continue to think that web development is often a better choice when you’re interacting with the internet already, but when you need to use other SDK features, the SDK offers some great ways to connect to the web.
As we’ve seen in this chapter, you have easy and intuitive access to the social web—that conglomeration of machines that’s connected via various public protocols. You should have no trouble creating projects that use the HTML and XML protocols, and even further flung protocols like JSON and SOAP are usable thanks to third-party libraries. That’ll cover most programmers’ needs, but for those of you who need to dig deeper, the SDK has you covered there too, thanks to Core Foundation classes.
In coming full circle, returning to the web technologies that opened this book, we’re also bringing this book to its end. But read on (if you haven’t already) for some helpful appendixes that list numerous SDK objects, talk more about getting your projects ready for market, and point toward other resources.
3.15.17.1