Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 57 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
57
Dung lượng
773,86 KB
Nội dung
CHAPTER 11: MapKit 383 #pragma mark - #pragma mark Reverse Geocoder Delegate Methods - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString( @"Error translating coordinates into location", @"Error translating coordinates into location") message:NSLocalizedString( @"Geocoder did not recognize coordinates", @"Geocoder did not recognize coordinates") delegate:self cancelButtonTitle:NSLocalizedString(@"Okay", @"Okay") otherButtonTitles:nil]; [alert show]; [alert release]; geocoder.delegate = nil; [geocoder autorelease]; } - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark { progressBar.progress = 0.5; progressLabel.text = NSLocalizedString(@"Location Determined", @"Location Determined"); MapLocation *annotation = [[MapLocation alloc] init]; annotation.streetAddress = placemark.thoroughfare; annotation.city = placemark.locality; annotation.state = placemark.administrativeArea; annotation.zip = placemark.postalCode; annotation.coordinate = geocoder.coordinate; [mapView addAnnotation:annotation]; [annotation release]; geocoder.delegate = nil; [geocoder autorelease]; } #pragma mark - #pragma mark Map View Delegate Methods - (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation { static NSString *placemarkIdentifier = @"Map Location Identifier"; if ([annotation isKindOfClass:[MapLocation class]]) { MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:placemarkIdentifier]; if (annotationView == nil) { annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:placemarkIdentifier]; } else annotationView.annotation = annotation; CHAPTER 11: MapKit 384 annotationView.enabled = YES; annotationView.animatesDrop = YES; annotationView.pinColor = MKPinAnnotationColorPurple; annotationView.canShowCallout = YES; [self performSelector:@selector(openCallout:) withObject:annotation afterDelay:0.5]; progressBar.progress = 0.75; progressLabel.text = NSLocalizedString(@"Creating Annotation", @"Creating Annotation"); return annotationView; } return nil; } - (void)mapViewDidFailLoadingMap:(MKMapView *)theMapView withError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error loading map", @"Error loading map") message:[error localizedDescription] delegate:nil cancelButtonTitle:NSLocalizedString(@"Okay", @"Okay") otherButtonTitles:nil]; [alert show]; [alert release]; } @end Let’s take it from the top, shall we? The first method in our class is the action method that gets called when the user presses a button. This is the logical starting point for our application’s logic, so let’s look at it first. As we’ve discussed before, we could have used the map view’s ability to track the user’s location, but we wanted to handle things manually to show more functionality. Therefore, we allocate and initialize an instance of CLLocationManager so we can determine the user’s location. We set self as the delegate, and tell the Location Manager we want the best accuracy available, before telling it to start updating the location. - (IBAction)findMe { CLLocationManager *lm = [[CLLocationManager alloc] init]; lm.delegate = self; lm.desiredAccuracy = kCLLocationAccuracyBest; [lm startUpdatingLocation]; Then, we unhide the progress bar and set the progress label to tell the user that we are trying to determine the current location. progressBar.hidden = NO; progressBar.progress = 0.0; progressLabel.text = NSLocalizedString(@"Determining Current Location", @"Determining Current Location"); Lastly, we hide the button so the user can’t press it again. CHAPTER 11: MapKit 385 button.hidden = YES; } Next, we have a private method called openCallout: that we’ll use a little later to select our annotation. We can’t select the annotation when we add it to the map view. We have to wait until it’s been added before we can select it. This method will allow us to select an annotation, which will open the annotation’s callout, by using performSelector:withObject:afterDelay:. All we do in this method is update the progress bar and progress label to show that we’re at the last step, and then use the MKMapView’s selectAnnotation:animated: method to select the annotation, which will cause its callout view to be shown. NOTE: We didn’t declare this method in our header file, nor did we declare it in a category or extension. Yet the compiler is happy. That’s because this method is located earlier in the file than the code that calls it, so the compiler knows about. If we were to move the openCallout: method to the end of the file, then we would get a compile time warning, and would have to declare the method in an extension or in our class’s header file. - (void)openCallout:(id<MKAnnotation>)annotation { progressBar.progress = 1.0; progressLabel.text = NSLocalizedString(@"Showing Annotation", @"Showing Annotation"); [mapView selectAnnotation:annotation animated:YES]; } In the viewDidLoad method, we gave you code to try out all three map types, with two of them commented out. This is just to make it easier for you to change the one you’re using and experiment a little. - (void)viewDidLoad { // uncomment different rows to change type mapView.mapType = MKMapTypeStandard; //mapView.mapType = MKMapTypeSatellite; //mapView.mapType = MKMapTypeHybrid; } Both viewDidUnload and dealloc are standard, so we won’t talk about them. After those, we get to our various delegate methods. First up is the location manager delegate method where we’re notified of the user’s location. We did something here that we didn’t do in Beginning iPhone 3 Development, which is to check the timestamp of newLocation and make sure it’s not more than a minute old. In the application we built in the first book, we wanted to keep getting updates while the application was running. In this application, we only want to know the current location once, but we don’t want a cached location. Location Manager caches locations so that it has quick access to the last known location. Since we’re only going to use one update, we want to discard any stale location data that was pulled from the location manager’s cache. - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation CHAPTER 11: MapKit 386 fromLocation:(CLLocation *)oldLocation { if ([newLocation.timestamp timeIntervalSince1970] < [NSDate timeIntervalSinceReferenceDate] - 60) return; Once we’ve made sure we have a fresh location, taken within the last minute, we then use the MKCoordinateRegionMakeWithDistance() function to create a region that shows one kilometer on each side of the user’s current location. MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 2000, 2000); We then adjust that region to the aspect ratio of our map view and then tell the map view to show that new adjusted region. MKCoordinateRegion adjustedRegion = [mapView regionThatFits:viewRegion]; [mapView setRegion:adjustedRegion animated:YES]; Now that we’ve gotten a non-cache location, we’re going to stop having the location manager give us updates. Location updates are a drain on the battery, so when you don’t want any more updates, you’ll want to shut location manager down, like so: manager.delegate = nil; [manager stopUpdatingLocation]; [manager autorelease]; Then we update the progress bar and label to let them know where we are in the whole process. This is the first of four steps after the Go button is pressed, so we set progress to .25, which will show a bar that is one-quarter blue. progressBar.progress = .25; progressLabel.text = NSLocalizedString(@"Reverse Geocoding Location", @"Reverse Geocoding Location"); Next, we allocate an instance of MKReverseGeocoder using the current location pulled from newLocation. We set self as the delegate and kick it off. MKReverseGeocoder *geocoder = [[MKReverseGeocoder alloc] initWithCoordinate:newLocation.coordinate]; geocoder.delegate = self; [geocoder start]; } NOTE: We didn’t release geocoder here, nor did we release the location manager in the findMe method. In both cases, we autorelease the objects in the last delegate method we use. If the location manager encounters an error, we just show an alert. Not the most robust error handling, but it’ll do for this. - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { NSString *errorType = (error.code == kCLErrorDenied) ? NSLocalizedString(@"Access Denied", @"Access Denied") : NSLocalizedString(@"Unknown Error", @"Unknown Error"); CHAPTER 11: MapKit 387 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error getting Location", @"Error getting Location") message:errorType delegate:self cancelButtonTitle:NSLocalizedString(@"Okay", @"Okay") otherButtonTitles:nil]; [alert show]; [alert release]; [manager release]; } Our alert view delegate method just hides the progress bar and sets the progress label to an empty string. For simplicity’s sake, we’re just dead-ending the application if a problem occurs. In your apps, you’ll probably want to do something a little more user- friendly. - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { progressBar.hidden = YES; progressLabel.text = @""; } If the reverse geocoding fails, we do basically the same thing we’d do if the location manager failed: put up an alert and dead-end the process. - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString( @"Error translating coordinates into location", @"Error translating coordinates into location") message:NSLocalizedString( @"Geocoder did not recognize coordinates", @"Geocoder did not recognize coordinates") delegate:self cancelButtonTitle:NSLocalizedString(@"Okay", @"Okay") otherButtonTitles:nil]; [alert show]; [alert release]; geocoder.delegate = nil; [geocoder autorelease]; } If the reverse geocoder succeeded, however, we update the progress bar and progress label to inform the user that we’re one step further along in the process. - (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark { progressBar.progress = 0.5; progressLabel.text = NSLocalizedString(@"Location Determined", @"Location Determined"); CHAPTER 11: MapKit 388 Then, we allocate and initialize an instance of MapLocation to act as the annotation that represents the user’s current location. We assign its properties from the returned placemark. MapLocation *annotation = [[MapLocation alloc] init]; annotation.streetAddress = placemark.thoroughfare; annotation.city = placemark.locality; annotation.state = placemark.administrativeArea; annotation.zip = placemark.postalCode; annotation.coordinate = geocoder.coordinate; Once we have our annotation, we add it to the map view and release it. [mapView addAnnotation:annotation]; [annotation release]; And, then, to be good memory citizens, we set the geocoder’s delegate to nil and autorelease it. geocoder.delegate = nil; [geocoder autorelease]; } When the map view for which we are the delegate needs an annotation view, it will call this next method. The first thing we do is declare an identifier so we can dequeue the right kind of annotation view, then we make sure the map view is asking us about a type of annotation that we know about. - (MKAnnotationView *) mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>) annotation { static NSString *placemarkIdentifier = @"Map Location Identifier"; if ([annotation isKindOfClass:[MapLocation class]]) { If it is, we dequeue an instance of MKPinAnnotationView with our identifier. If there are no dequeued views, we create one. We could also have used MKAnnotationView here instead of MKPinAnnotationView. In fact, there’s an alternate version of this project in the project archive that shows how to use MKAnnotationView to display a custom annotation view instead of a pin. MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:placemarkIdentifier]; if (annotationView == nil) { annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:placemarkIdentifier]; } If we didn’t create a new view, it means we got a dequeued one from the map view. In that case, we have to make sure the dequeued view is linked to the right annotation. else annotationView.annotation = annotation; Then we do some configuration. We make sure the annotation view is enabled so it can be selected, we set animatesDrop to YES because this is a pin view, and we want it to drop onto the map the way pins are wont to do. We set the pin color to purple, and make sure that it can show a callout. CHAPTER 11: MapKit 389 annotationView.enabled = YES; annotationView.animatesDrop = YES; annotationView.pinColor = MKPinAnnotationColorPurple; annotationView.canShowCallout = YES; After that, we use performSelector:withObject:afterDelay: to call that private method we created earlier. We can’t select an annotation until its view is actually being displayed on the map, so we wait half a second to make sure that’s happened before selecting. This will also make sure that the pin has finished dropping before the callout is displayed. [self performSelector:@selector(openCallout:) withObject:annotation afterDelay:0.5]; We need to update the progress bar and text label to let the user know that we’re almost done. progressBar.progress = 0.75; progressLabel.text = NSLocalizedString(@"Creating Annotation", @"Creating Annotation"); Then we return the annotation view. return annotationView; } If the annotation wasn’t one we recognize, we return nil and our map view will use the default annotation view for that kind of annotation. return nil; } And, lastly, we implement mapViewDidFailLoadingMap:withError: and inform the user if there was a problem loading the map. Again, our error checking in this application is very rudimentary; we just inform the user and stop everything. - (void)mapViewDidFailLoadingMap:(MKMapView *)theMapView withError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error loading map", @"Error loading map") message:[error localizedDescription] delegate:nil cancelButtonTitle:NSLocalizedString(@"Okay", @"Okay") otherButtonTitles:nil]; [alert show]; [alert release]; } Linking the Map Kit and Core Location Frameworks Before you can build and run your app, you need to right-click on the Frameworks folder in the Groups & Files pane and select Existing Frameworks… from the Add submenu. Select CoreLocation.framework and MapKit.framework and click the Add… button. CHAPTER 11: MapKit 390 You should now be able to build and run your application, so do that, and try it out. Try experimenting with the code. Change the map type, add more annotations, or try experimenting with custom annotation views. Go East, Young Programmer That brings us to the end of our discussion of MapKit. You’ve seen the basics of how to use MapKit, annotations, and the reverse geocoder. You’ve seen how to create coordinate regions and coordinate spans to specify what area the map view should show to the user, and you’ve learned how to use MapKit’s reverse geocoder to turn a set of coordinates into a physical address. Now, armed with your iPhone, MapKit, and sheer determination, navigate your way one page to the East, err… right, so that we can talk about in-application e-mail. 391 391 Chapter Sending Mail Ever since the first public release of the iPhone SDK, applications have always had the ability to send e-mail. Unfortunately, prior to iPhone SDK 3.0, doing so meant crafting a special URL and then launching the iPhone’s Mail application, which has the side effect of quitting your own application. This is obviously less than ideal, forcing a user to choose between sending an e-mail and continuing to use your application. Fortunately, the new MessageUI framework allows your user access to e-mail without leaving your application. Let’s take a look at how this works. This Chapter’s Application In this chapter, we’re going to build an application that lets the user take a picture using their iPhone’s camera or, if they don’t have a camera because they’re using an iPod touch or the Simulator, then we’ll allow them to select an image from their photo library. We’ll then take the resulting image and use the MessageUI framework to let our user e- mail the picture to a friend without leaving our application. Our application’s interface will be quite simple (Figure 12–1). It will feature a single button to start the whole thing going, and a label to give feedback to the user, once the e-mail attempt is made. Tapping the button will bring up the camera picker controller, in a manner similar to the sample program in Chapter 16 of Beginning iPhone 3 Development (Apress, 2009). Once our user has taken or selected an image, they’ll be able to crop and/or scale the image (Figure 12–2). Assuming they don’t cancel, the image picker will return an image, and we’ll display the mail compose view (Figure 12– 3), which allows the user to compose their e-mail message. We’ll pre-populate that view with text and the selected image. Our user will be able to select recipients and change the subject or message body before sending the message. When they’re all done, we’ll use the label in our interface to give feedback about whether the e-mail was sent. 12 CHAPTER 12: Sending Mail 392 Figure 12–1. Our chapter’s application has a very simple user interface consisting of a button and a single label (not shown here) Figure 12–2. The user can take a picture with the camera or select an image from their photo library, and then crop and scale the image [...]... Fu 4 03 404 CHAPTER 12: Sending Mail 405 13 Chapter iPod Library Access The iPhone, in addition to being a phone, is a first-class music player as well Out of the box, people can (and do) use it to listen to music, podcasts, and audio books Of course, it goes without saying that the iPod touch is also a music player iPhone SDK programs have always been able to play sounds and music, but with the 3. 0... setToRecipients:[NSArray arrayWithObjects:@"jeff@iphonedevbook.com", "@dave@iphonedevbook.com", nil]; Set the other two types of recipients in the same manner, though you’ll use the methods setCcRecipients: for cc: recipients and setBccRecipients: for bcc: recipients [mc setCcRecipients:[NSArray arrayWithObject:@"dave@iphonedevbook.com"]]; [mc setBccRecipients:[NSArray arrayWithObject:@"secret@iphonedevbook.com"]]; CHAPTER... e-mail, perhaps because you need to support older versions of the iPhone OS that don’t have the MessageUI framework available Here is how you would craft a mailto: URL to launch Mail.app with a new e-mail message, with the fields prepopulated: NSString *to = @"mailto:jeff@iphonedevbook.com"; NSString *cc = @"?cc=dave@iphonedevbook.com,secret@iphonedevbook.com"; NSString *subject = @"&subject=Hello World!";... of the options will be Save Image This will add the selected image to your iPhone s photo library In addition, note that you will not be able to send e-mail from within the simulator You’ll be able to create the e-mail, and the simulator will say it sent it, but it’s all lies The e-mail just ends up in the circular file 39 3 39 4 CHAPTER 12: Sending Mail The MessageUI Framework In-application e-mail... (Figure 13- 1) Choose specific songs using the iPod’s media picker, which is essentially the iPod application presented modally from within our application (Figure 13- 2) Using the media picker, our user can select audio tracks by album, song, or playlist, or using any other approach that the iPod application supports (with the exception of Cover Flow) 405 406 CHAPTER 13: iPod Library Access Figure 13- 1 Our... we’re offering to our users (and more) is already available in the iPod application on the iPhone or the Music application on the iPod touch But writing it will allow us to explore almost all of the tasks your own application might ever need to perform with regard to the iPod library CAUTION: This chapter’s application must be run on an actual iPhone or iPod touch The iPhone simulator does not have access... Round Rect Button and place it anywhere on the window titled View Double-click the button and give it a title of Go Control-drag from the button to File’s Owner and select the selectAndMailPic action 39 7 39 8 CHAPTER 12: Sending Mail Next, grab a Label from the library and drag it to the View window as well Place the label above the button and resize it so it stretches from the left margin to the right... objectForKey: UIImagePickerControllerEditedImage]; [self performSelector:@selector(mailImage:) withObject:image afterDelay:0.5]; message.text = @""; } 401 402 CHAPTER 12: Sending Mail NOTE: In Beginning iPhone 3 Development, we implemented a different delegate method called imagePickerController:didFinishPickingImage:editingInfo: That method has been deprecated in favor of the newer method imagePickerController:didFinish... mailComposeController:didFinishWithResult:error: gets called As with most 39 5 39 6 CHAPTER 12: Sending Mail delegate methods, the first parameter is a pointer to the object that called the delegate method The second parameter is a result code that tells us the fate of the outgoing email, and the third is an NSError instance that will give us more detailed information if a problem was encountered Regardless... use the artistsQuery class method to create an instance of MPMediaQuery configured, like this: MPMediaQuery *artistsQuery = [MPMediaQuery artistsQuery]; CHAPTER 13: iPod Library Access Table 13- 1 lists the factory methods on MPMediaQuery Table 13- 1 MPMediaQuery Factory Methods Factory Method Included Media Types Grouped/Sorted By albumsQuery Music Album artistsQuery Music Artist audiobooksQuery Audio . did something here that we didn’t do in Beginning iPhone 3 Development, which is to check the timestamp of newLocation and make sure it’s not more than a minute old. In the application we built. release of the iPhone SDK, applications have always had the ability to send e-mail. Unfortunately, prior to iPhone SDK 3. 0, doing so meant crafting a special URL and then launching the iPhone s Mail. NSLocalizedString(@"Location Determined", @"Location Determined"); CHAPTER 11: MapKit 38 8 Then, we allocate and initialize an instance of MapLocation to act as the annotation that represents