1. Trang chủ
  2. » Công Nghệ Thông Tin

more iphone 3 development phần 7 ppt

57 296 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 57
Dung lượng 10,3 MB

Nội dung

CHAPTER 9: Online Play: Bonjour and Network Streams 326 @"Unable to publish Bonjour service(%@/%@)"), errorDomain, errorCode] ]; [theNetService stop]; } - (void)netServiceDidStop:(NSNetService *)netService { self.netService.delegate = nil; self.netService = nil; } Next up is an NSNetService delegate that is called whenever an error is encountered. This is called if an error is encountered either with publishing a service or resolving one. All we do is show an alert. #pragma mark - #pragma mark Net Service Delegate Methods (General) - (void)handleError:(NSNumber *)error withService:(NSNetService *)service { [self showErrorAlertWithTitle:NSLocalizedString(@"A network error occurred.", @"A network error occurred.") message:[NSString stringWithFormat: NSLocalizedString( @"An error occurred with service %@.%@.%@, error code = %@", @"An error occurred with service %@.%@.%@, error code = %@"), [service name], [service type], [service domain], error]]; } There are two delegate methods related to resolving discovered services: one is called if the service could not be resolved, and one is called if it resolves successfully. If it fails to resolve, we just show an alert and stop trying to resolve the service. #pragma mark - #pragma mark Net Service Delegate Methods (Resolving) - (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict { NSNumber *errorDomain = [errorDict valueForKey:NSNetServicesErrorDomain]; NSNumber *errorCode = [errorDict valueForKey:NSNetServicesErrorCode]; [self showErrorAlertWithTitle:NSLocalizedString(@"Unable to connect", @"Unable to connect") message:[NSString stringWithFormat: NSLocalizedString(@"Could not start game with remote device (%@/%@)", @"Could not start game with remote device (%@/%@)"), errorDomain, errorCode] ]; [sender stop]; } If it resolved successfully, then we stop listening for new connections and get the stream pair for the connection. If we’re not able to get the stream pair, we show an error alert; otherwise, we create an OnlineSession object with the stream pair. - (void)netServiceDidResolveAddress:(NSNetService *)service { [self.onlineSessionListener stopListening]; self.onlineSessionListener = nil; NSInputStream *tempIn = nil; NSOutputStream *tempOut = nil; if (![service getInputStream:&tempIn outputStream:&tempOut]){ [self showErrorAlertWithTitle:NSLocalizedString(@"Unable to connect", @"Unable to connect") message:NSLocalizedString( @"Could not start game with remote device", CHAPTER 9: Online Play: Bonjour and Network Streams 327 @"Could not start game with remote device") ]; return; } OnlineSession *theSession = [[OnlineSession alloc] initWithInputStream:tempIn outputStream:tempOut]; theSession.delegate = self; self.onlineSession = theSession; [theSession release]; } When an OnlineListener detects a connection, it notifies its delegate. In that case, we also create an OnlineSession object with the stream pair we got from the listener. #pragma mark - #pragma mark Online Session Listener Delegate Methods - (void) acceptedConnectionForListener:(OnlineListener *)theListener inputStream:(NSInputStream *)theInputStream outputStream:(NSOutputStream *)theOutputStream { OnlineSession *theSession = [[OnlineSession alloc] initWithInputStream:theInputStream outputStream:theOutputStream]; theSession.delegate = self; self.onlineSession = theSession; [theSession release]; } Our OnlineSession object, regardless of whether it was created by resolving a service or by accepting a connection from another machine, will call onlineSessionReadyForUse: when both streams are open. In this method, we check to see if we’re still presenting a modal view controller, which would be the case if we received a connection from another machine; if so, we dismiss it. Then we start a new game. #pragma mark - #pragma mark Online Session Delegate Methods - (void)onlineSessionReadyForUse:(OnlineSession *)session { if (self.modalViewController) [self dismissModalViewControllerAnimated:YES]; [self startNewGame]; } When we receive data from the OnlineSession, all we need to do is pass that on to the handleReceivedData: method. - (void)onlineSession:(OnlineSession *)session receivedData:(NSData *)data { [self handleReceivedData:data]; } If any of the three OnlineSessionDelegate error methods are called, we throw up an error alert and kill the session. - (void)onlineSession:(OnlineSession *)session encounteredReadError:(NSError *)error { [self showErrorAlertWithTitle:NSLocalizedString(@"Error reading", @"Error Reading") message:NSLocalizedString(@"Could not read sent packet", @"Could not read sent packet")]; self.onlineSession = nil; CHAPTER 9: Online Play: Bonjour and Network Streams 328 } - (void)onlineSession:(OnlineSession *)session encounteredWriteError:(NSError *)error { [self showErrorAlertWithTitle:NSLocalizedString(@"Error Writing", @"Error Writing") message:NSLocalizedString(@"Could not send packet", @"Could not send packet")]; self.onlineSession = nil; } - (void)onlineSessionDisconnected:(OnlineSession *)session { [self showErrorAlertWithTitle:NSLocalizedString(@"Peer Disconnected", @"Peer Disconnected") message:NSLocalizedString( @"Your opponent disconnected or otherwise could not be reached.", @"Your opponent disconnected or otherwise could not be reached")]; self.onlineSession = nil; } @end WHAT ABOUT INTERNET PLAY? If you want to offer play over the Internet, the process is almost exactly the same. You still need to listen on a port, and you still use streams to exchange data with the remote machine. Generally speaking, you do not use Bonjour to advertise services over the Internet, though. Typically, a dedicated server will be used to find opponents or, more rarely, users will be asked to type in the address and port to which they want to connect. To find out more about getting a stream connection to a remote machine based on DNS name or IP address and port, you should read Tech Note QA1652, which is available at http://developer.apple.com/iphone/library/qa/qa2009/qa1652.html. Time to Play And with that marathon of changes, we have now implemented online play in our TicTacToe application. You can select Build and Run from the Build menu to try it out. About time, huh? Online play is significantly more complex to implement than GameKit over Bluetooth, but there’s good news. The OnlineSession and OnlineListener objects we just wrote are completely generic. Copy them to a new project, and you can use them unchanged. That means your next application that needs to support network play will be almost as easy to write as it would be to use GameKit. Before we leave the topic of networking completely, we have one more chapter of network goodness for you. We’re going to show you a variety of ways to retrieve information from web servers and RESTful web services. 329 329 Chapter Working with Data from the Web As you saw in the last chapter, writing code to communicate over a network can be complex and, at times, difficult. Fortunately, for many common network-related tasks, Apple has provided higher-level methods and objects that will make your life considerably easier. One fairly common task when you’re writing software for a device that’s pretty much always connected to the Internet is to retrieve data from web servers. There is a large amount of data available for applications to use on the World Wide Web, and there are countless reasons why an iPhone application might want to pull data from the Web. NOTE: The applications we’re writing in this chapter will work just fine on the simulator. But, as you might expect, since those applications will be retrieving data from the Web, they’ll only work if the computer on which the simulator is running has an active connection to the Internet. There are a number of techniques you can use to grab data from web servers. In this chapter, we’re going to show you three of them. We’ll first show you how to leverage special methods that exist in several Foundation classes that allow you to retrieve data based on a URL in just a line or two of code. We’ll expand on that and show you how to take more control over the process so that you can detect when errors occur. Next, we’ll show you how to pull data asynchronously, so your application can do other things while data is being retrieved in the background. And finally, we’ll learn how to make different types of HTTP requests and pass form parameters so you can retrieve data from web applications and web services as well as static files. Since each of these topics stands alone, we’ll build our chapter application-iteratively. We’ll discuss one type of retrieval, then add it to the application. We’ll start by setting up an application skeleton. Next, we’ll add URL-based methods to retrieve both an image and text from the Web. Then we’ll talk about doing a more robust form of data retrieval, and then add code to our application to retrieve the same image 10 CHAPTER 10: Working with Data from the Web 330 and text file using that approach. After that, we’ll talk about asynchronous data retrieval and then add code to our application to retrieve the text and image in the background. You can look at Figure 10–1 to see what our application will look like when done. Figure 10–1. One of the two applications we’ll build in this chapter The top row of buttons will retrieve an image file from a web server in one of three different ways. The bottom row of buttons will retrieve a text document in one of three different ways. Once we’re done with those different ways of retrieving static data, we’ll move on to forms and various HTTP request types. Then we will build another small application that uses both kinds of form parameters and two different request types (Figure 10–2). CHAPTER 10: Working with Data from the Web 331 Figure 10–2. The second application we’re going to build in this chapter shows how to change the request type and how to pass form parameters Setting Up the Application Skeleton We’re going to start by creating an application skeleton with stub methods for each of the tasks that we’re going to implement in the first application. A stub method (sometimes referred to as just a stub) is typically an empty method, or one with only one or two lines of code designed to act as a placeholder for a method that you plan to add later. This allows you to set up your user interface before you’re ready to write the code behind it. As we discuss the different ways to retrieve data, we will add code to these stubs. In Xcode, create a new project, select the View-based Application template, and call the new project WebWork. Once the project is open, find the project archives that accompany this book and look in the 10 – WebWork folder for the images called blue_get.png, green_get.png, lavender_get.png, text.png, and image.png and add them all to your project. These are the images you’ll need for the buttons as well as the text and image icons that appear to the left of the buttons in Figure 10–1. Declaring Actions and Outlets Single-click on WebWorkViewController.h so we can add our outlet and action declarations. Replace the existing contents with the following code: CHAPTER 10: Working with Data from the Web 332 #import <UIKit/UIKit.h> #define kImageURL @"http://iphonedevbook.com/more/10/cover.png" #define kTextURL @"http://iphonedevbook.com/more/10/text.txt" typedef enum RequestTypes { kRequestTypeImage, kRequestTypeText, } RequestType; @interface WebWorkViewController : UIViewController { UIActivityIndicatorView *spinner; UIImageView *imageView; UITextView *textView; NSMutableData *receivedData; RequestType requestType; } @property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner; @property (nonatomic, retain) IBOutlet UIImageView *imageView; @property (nonatomic, retain) IBOutlet UITextView *textView; @property (nonatomic, retain) NSMutableData *receivedData; - (void)clear; - (IBAction)getImageUsingNSData; - (IBAction)getImageSynchronously; - (IBAction)getImageAsynchronously; - (IBAction)getTextUsingNSString; - (IBAction)getTextSynchronously; - (IBAction)getTextAsynchronously; @end We start off by defining two constants that point to an image file and a text file that we’ve hosted on the Internet for your use. This is the data that we’ll be pulling into our application. Feel free to use different URLs if you prefer. #define kImageURL @"http://iphonedevbook.com/more/10/cover.png" #define kTextURL @"http://iphonedevbook.com/more/10/text.txt" Next, we define a new type along with an enum. In some parts of our code, we will be using delegate methods (surprise!), and we will need a way to know in one of those delegate methods whether the data being we’re retrieving holds an image or text. While there are ways to determine that from the web server’s response (which we’ll see later in the chapter), just keeping track of which we’ve requested is a lot easier and more efficient. typedef enum RequestTypes { kRequestTypeImage, kRequestTypeText, } RequestType; We have three views that we’ll need outlets to so that we can show the returned data. The UIImageView will be used to show the retrieved image, the UITextView will be used CHAPTER 10: Working with Data from the Web 333 to display the retrieved text, and the UIActivityIndicatorView is that white spinning doohickey that tells the user that some action is in progress (you’ll know it when you see it). When we retrieve the data asynchronously, we’ll show the activity indicator so that the user knows we’re in the process of retrieving the data they requested. Once we have the data, we’ll hide the activity indicator and show the image or text that was requested. @interface WebWorkViewController : UIViewController { UIActivityIndicatorView *spinner; UIImageView *imageView; UITextView *textView; We also declare an instance of NSMutableData that will be used to store the data when fetching asynchronously. When we do that, a delegate method that we will implement will be called repeatedly and provided with small chunks of the requested data. We will accumulate those chunks in this instance so that when the process is complete, we’ll have the whole image or text file. NSMutableData *receivedData; And, here’s where we’ll keep track of whether an image or text was last requested. RequestType requestType; We also declare properties for our instance variables, using the IBOutlet keyword for those that will need to be connected to objects in Interface Builder. @property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner; @property (nonatomic, retain) IBOutlet UIImageView *imageView; @property (nonatomic, retain) IBOutlet UITextView *textView; @property (nonatomic, retain) NSMutableData *receivedData; And then we have our methods. The first one is just used to clear the requested data so that the application can be used again without restarting. - (void)clear; And we have six action methods, one for each of the buttons you can see in Figure 10– 1. Since each button represents a different way to retrieve one kind of data, it makes sense to give each of the buttons its own action method. - (IBAction)getImageUsingNSData; - (IBAction)getImageSynchronously; - (IBAction)getImageAsynchronously; - (IBAction)getTextUsingNSString; - (IBAction)getTextSynchronously; - (IBAction)getTextAsynchronously; Designing the Interface Now that we have our actions and outlets in place, make sure you save first, then double-click WebWorkViewController.xib to open up the file in Interface Builder. Let’s start off by dragging an Image View from the library over to the window labeled View. Interface Builder will resize the image view to take up the whole window, which CHAPTER 10: Working with Data from the Web 334 isn’t what we want this time, so press 3 to bring up the size inspector, change the X and Y value each to 20, set W to 280, and set H to 255. Then, control-drag from File’s Owner to the image view and select the imageView outlet. Press 1 and use the attribute inspector to change the Mode from Center to Aspect Fit so that the image will be resized to fit. Now, drag a Text View from the library to the View window. Place it in exactly the same location as the image view and make it exactly the same size. Once it’s placed, control- drag from File’s Owner to the text view and select the textView outlet. Double-click the text view so that the text it contains is editable, make sure all the text is selected, and hit the delete button. In the attribute inspector, uncheck the box that says Editable so that our user can’t change the downloaded text. In the library, look for an Activity Indicator View and drag one to the View window. Use the blue guidelines to line it up with the horizontal and vertical centers of the text and image views you already added. Then, control-drag from File’s Owner to the activity indicator and select the spinner outlet. Press 1 to bring up the attribute inspector and check Hide When Stopped so that when the indicator is not spinning, it won’t be visible. Now, drag another Image View to the view. Place it somewhere in the bottom half of the screen; the exact placement doesn’t matter for now. Press 1and use the attribute inspector to select the text.png for the Image field. Press = to resize the image view to match the image, then place the resized image view in the lower-left of the window, using the blue guidelines to place it against the bottom and left margins. Bring over another Image View and select image.png for its image. Use = to resize the image view and then place it above the image view you placed a moment ago, using Figure 10–1 as a guide. Next, bring over a Round Rect Button from the library, and use the size inspector (3) to change both the height and width of the button to 57 pixels. Place the button to the right of the image.png image view. Now, use the attributes inspector to change the button’s type from Rounded Rect to Custom and select blue_get.png from the Image pop-up. Option-drag the button to the right to create a second one, then repeat to create a third button. Change the image of the second button to green_get.png and change the image of the third button to lavender_get.png. Finally, select all three buttons and option-drag them to create three new buttons below the first set of buttons. Use Figure 10–1 as a guide to help you place everything just so. Now, bring over a Label over from the library, and place it above the left-most button, the blue one. Change the font size to 14 points (you can change the font size using the fonts palette T) and change the text to Object. Now option-drag the label to create a second and third copy, placing one above the second and third column of buttons. Change the second label to read Sync, and the third label to read Async. Again, use Figure 10–1 as a guide. Now, control-drag from all six of the buttons to File’s Owner and select the action methods that match the button’s position. For the top-left button, for example, you CHAPTER 10: Working with Data from the Web 335 should select getImageUsingNSData, and for the bottom-left button you should select getTextUsingNSString. Once you have connected all six buttons to the appropriate action method, save the nib and head back to Xcode. Implementing the Stubs Now we’re going to write our implementation file, but aren’t going to write any of the actual code to retrieve the data yet. We’re just putting in placeholders so we have a place to add the code later in the chapter. Single-click WebWorkViewController.m and replace the current contents with the following: #import "WebWorkViewController.h" @implementation WebWorkViewController @synthesize spinner; @synthesize imageView; @synthesize textView; @synthesize receivedData; - (void)clear { imageView.hidden = YES; textView.hidden = YES; } - (IBAction)getImageUsingNSData { NSLog(@"Entering %s", __FUNCTION__); } - (IBAction)getImageSynchronously { NSLog(@"Entering %s", __FUNCTION__); } - (IBAction)getImageAsynchronously { NSLog(@"Entering %s", __FUNCTION__); } - (IBAction)getTextUsingNSString { NSLog(@"Entering %s", __FUNCTION__); } - (IBAction)getTextSynchronously { NSLog(@"Entering %s", __FUNCTION__); } - (IBAction)getTextAsynchronously { NSLog(@"Entering %s", __FUNCTION__); } - (void)viewDidUnload { self.spinner = nil; self.imageView = nil; self.textView = nil; } - (void)dealloc { [...]... controls the user interface) until it has finished downloading the data If you’re pulling down a small text file, that might not be a big deal, but if you’re pulling down a high-res image or a video 33 7 33 8 CHAPTER 10: Working with Data from the Web file, it’s a very big deal Your user interface will become unresponsive and your application will be unable to do anything else until the data has all been... can’t be reached Another way you can test is to change the URL to point to an object that doesn’t exist on the server, like so: #define kImageURL #define kTextURL @"http://iphonedevbook.com /more/ 10/foo.png" @"http://iphonedevbook.com /more/ 10/foo.txt" Figure 10–5 If the network connecton isn’t working, or the remote server can’t be reached, we’re able to tell the user that That is much better, but we still... button to File’s Owner and select the action named doGetRequest Repeat with the button on the right and connect to the doPostRequest action Save the nib and go back to Xcode 35 3 35 4 CHAPTER 10: Working with Data from the Web Figure 10 7 Use this as a guide when building the RequestTypes application interface The exact placement isn’t important Single-click RequestTypesViewController.m and replace the contents... press the Get button with foo in the Parameter field and bar in the Value field, it’s as if you sent out this URL: http://iphonedevbook.com /more/ 10/echo.php?foo=bar If you press the Post button, it’s as if you had submitted an HTML form with an action of http://iphonedevbook.com /more/ 10/echo.php that contained a text field (or other control) named foo and the user entered a value of bar into that field... right in your own application 35 9 11 Chapter MapKit iPhones have always had a way to determine where in the world they are Even though the original iPhone didn’t have GPS, it did have a Maps application and was able to represent its approximate location on the map using cell phone triangulation or by looking up its WiFi IP address in a database of known locations Prior to SDK 3, there was no way to leverage... request, how do you use it to get data? In addition to the request, we also need a connection, which is represented by the class NSURLConnection To request data synchronously, however, we don’t actually 33 9 34 0 CHAPTER 10: Working with Data from the Web have to create a connection, we can just use a class method on NSURLConnection to send our request and retrieve the data, like so: NSHTTPURLResponse* response... the data Not a huge deal here where we’re only pulling a few kilobytes of data, but potentially a very big deal in many situations Let’s look at how to fix that by requesting the data asynchronously 34 3 34 4 CHAPTER 10: Working with Data from the Web Figure 10–6 If we are able to reach the server, but the URL doesn’t point to what we think it does, we’re also able to report that back to our user or take... does not Unless you add this line of code, POST parameters will not pass correctly when making a PUT request: [req setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; 35 7 35 8 CHAPTER 10: Working with Data from the Web In every other respect (except the request type, of course), the code for PUT and POST requests is exactly the same The code for DELETE requests will usually... First, we create the URL and request, then use those to create an instance of NSURLConnection, specifying self as the delegate We check to make sure the connection object is not nil, which would indicate 34 7 34 8 CHAPTER 10: Working with Data from the Web that the server could not be reached, and if we have a valid connection, we allocate our NSMutableData instance to hold the data we’re about to start receiving... functionality to any application NOTE: The application we build in this chapter will run just fine in the iPhone Simulator; however, the Simulator won’t report your actual location Instead, it always returns the address of Apple’s corporate headquarters at 1 Infinite Loop in Cupertino, California 35 9 36 0 CHAPTER 11: MapKit TERMS The MapKit framework uses Google services to provide map data As a result, . from the Web 33 2 #import <UIKit/UIKit.h> #define kImageURL @"http://iphonedevbook.com /more/ 10/cover.png" #define kTextURL @"http://iphonedevbook.com /more/ 10/text.txt". URLs if you prefer. #define kImageURL @"http://iphonedevbook.com /more/ 10/cover.png" #define kTextURL @"http://iphonedevbook.com /more/ 10/text.txt" Next, we define a new type. server, like so: #define kImageURL @"http://iphonedevbook.com /more/ 10/foo.png" #define kTextURL @"http://iphonedevbook.com /more/ 10/foo.txt" Figure 10–5. If the network

Ngày đăng: 12/08/2014, 21:20