iOS 6 Application Development For Dummies (2013)

Part V. Adding the Application Content

Chapter 19. Finding a Location

In This Chapter

arrow Setting up and implementing the Find controller

arrow Finding map coordinates from an address

arrow Displaying the found location on the map

arrow Using blocks in your own code

It’s pretty useful when traveling to be able to enter a location and have that display on a map. Although you can do that in many of the map applications currently available, it does take you out of the app you are in. What’s more, you can’t take that information and then do something with it in your own app, such as display it with all your other annotations.

As I explain in the previous chapter, geocoding allows you to take an address and turn it into a map coordinate. This enables you to add a feature to RoadTrip that allows the user to enter an address, or even just the name of a well-known landmark, and display it on the map. (Reverse geocoding, demonstrated in the previous chapter, allows you to take a map coordinate — your current location, for example — and turn it into an address.) In this chapter, you find out how to enter a location (an address or point of interest) and display it on a map as an annotation.

Setting Up the Find Controller

You already have one piece of the geocoding puzzle in place on your storyboard; I’m talking about the appropriately named Find controller. The trick now is to add the custom controller that will implement the Find features you want.

Adding the custom view controller

To add the FindController to the RoadTrip project, follow the same steps you have several times before:

1. In the Project navigator, select the View Controller Classes group and then either right-click the selection and choose New File from the menu that appears or choose FileNewNew File from the main menu (or press Command Key+N).

Whatever method you choose, you’re greeted by the New File dialog.

2. In the left column of the dialog, select Cocoa Touch under the iOS heading, select the Objective-C class template in the top-right pane, and then click Next.

You’ll see a dialog that will enable you to choose the options for your file.

3. Enter FindController in the Class field, choose MapController from the Subclass Of drop-down menu (FindController is a subclass of MapController with a little more functionality), and make sure that the Target for iPad check box is selected and that With XIB for User Interface is deselected. Click Next.

4. In the Save sheet that appears, click Create.

Setting up FindController in the MainStoryboard_iPad

Because Find Controller’s user interface is identical to the Map Controller’s, setting up the Find controller in the storyboard is virtually the same as setting up the Map controller in Chapter 17. You do, however, have two choices here. First, you can simply duplicate what you did inChapter 17 to create the Map Controller user interface, or you can duplicate Map Controller in the storyboard and adjust a few values in the Identity and Attribute inspectors.

Personally, I vote for the latter, but feel free to go with the former if you feel the need. Follow these steps to do the latter — for example, duplicate the Map Controller and convert the copy to a Find Controller.

1. In the Project navigator, select the iPad’s MainStoryboard file and then select Map Controller in the Map Controller – Map Scene in the Document Outline. Then select the Find Controller scene in the actual Storyboard canvas.

2. Chose EditDuplicate from the main menu (or press Command Key+D).

Doing so adds a copy of the selected element. Duplicate puts the duplicate Map controller directly on top of the original Map controller and partially on top of the Find controller you added to the storyboard in Chapter 12. In Figure 19-1, I’ve moved the duplicate a bit off to the side so you can see it. I’ve also highlighted the entry for it in the Document Outline.

The error you see in the Interface Builder Jump bar and in the Activity Viewer informs you that you now have “Multiple view controllers with the identifier “Map” — duplicate really does mean duplicate. You’ll fix this as you go along.

3. Delete the old Find Controller (I know I had you add it, but it really was just a placeholder) by selecting it either on the Canvas or in the Document Outline and then dragging the duplicated Map to the old Find Controller’s place on the Canvas (see Figure 19-2).

image  4. Select the duplicated Map controller on the Canvas or in the Document Outline, open the Identity inspector by clicking its icon in the Inspector selector bar, and then select FindController from the Inspector Class drop-down menu, as I have in Figure 19-3.

image  5. Move to the Attributes inspector by clicking its icon in the Inspector selector bar and then enter Find in the inspector’s Title field, as well as in the Identity inspector’s Storyboard ID field.

The errors that I mention back in Step 2 go away, as you can see in Figure 19-4.

image

Figure 19-1: The duplicate Map controller.

image

Figure 19-2: Delete the FindController and move the duplicated Map Controller to its place on the Canvas.

image

Figure 19-3: Make this use the FindController class.

image

Figure 19-4: The new FindController in the storyboard.

You’ll find that all the required connections to the MapView and toolbar outlets as well as the mapType action are (fortunately, or maybe even miraculously) in place.

Implementing the Find Controller

For your geocoding functionality to work, you’re going to need to do several things in the Find controller. Most of what you need to do revolves around getting the text the user enters. You’ll also have to have the text geocoded and have the geocoded location implemented as anAnnotation by the Trip model object, which the Find controller will then add to the map.

Getting the text

In Chapter 14, you format the Find cell with a label and a Text field. In this chapter, you set things up so that the Master View controller can get the text a user enters and pass it on to the Find controller to, well, find that location.

To access the text, you first need to create an outlet for the Text field. Follow these steps:

1. Select MainStoryboard in the Project navigator.

2. Select the Master View controller in the Document Outline.

image  3. Select the Assistant in the Editor selector, and if the RTMasterViewController.h file doesn’t appear, navigate to it using the Jump bar.

4. In the Document Outline, open the disclosure triangle for the second Table View section in the Master View Controller – Master Scene — to get to the Table View cell holding the Find label and text.

5. Open that cell’s disclosure triangle to display the Text field, and then control-drag from the Text field to the Master View controller interface (in the Assistant editor) between the @interface and @end compiler directives, and add an outlet named findText.

You can see it in Figure 19-5.

Yes, you could have dragged from the Text field in the cell on the Canvas, but in case you can’t find it, this is another way to create the outlet.

image

Figure 19-5: Creating a textField outlet.

A UITextField object is a control that displays editable text and sends a message to its delegate when the user presses the Return key. You typically use a UITextField object to enable the user to enter small amounts of text and then do something with it — like search for something or add a new contact.

image  Select the Text field, select the Standard editor in the Editor selector on the Xcode toolbar, and then open the Utility area. You can set a number of Text field properties in the Attributes inspector, as you see in Figure 19-6. Here, I’ve selected the Appears While Editing option from the Clear Button drop-down menu, I’ve selected the Clear When Editing Begins check box, and I’ve selected Go from the Return Key drop-down menu to change what’s displayed in the Return key. Go will provide a visual clue to the user on how to get RoadTrip to go find that location.

You may be thinking that, for all this to work, you’d need some kind of Target-Action design pattern that would make sure that some event gets triggered when text gets entered. Otherwise, how would you know when the user has entered some text? Also, how do you get the keyboard to show, and then hide? To answer that, I explain what happens with a UITextField.

image

Figure 19-6: Change the Return key to Go.

Scrolling a view

The way to be notified that the keyboard is about to appear is to register for the UIKeyboardWillShowNotification. That notification is posted by the UIWindow class. You pass a block object to the Notification Center and tell it to execute the block when the notification is posted. In that block, you determine the amount you’ll need to scroll the view, and you scroll the view by assigning a new frame in an animation block.

When a user taps in a UITextField, it becomes the first responder (explained in Chapter 4), and the keyboard automatically rises to allow the user to enter text — you don’t have to do a thing to make that happen. Although normally you’re responsible for scrolling the view if the keyboard will cover the text field — which in this case will only happen when running the app on the iPhone — because the text field is in a Table View cell, scrolling the view is done by the Table view.

When the user is done entering text, he taps the Return key — the Return key whose label you managed to change to Go.

When the Go key (née Return key) is tapped, the text field determines whether it has a delegate and whether the delegate has implemented a textFieldShouldReturn: method — one of the optional UITextFieldDelegate protocol methods. If the delegate has done so, it sends the delegate the textFieldShouldReturn: message. So textFieldShouldReturn: is the place to capture the text.

Long story short: To capture the text and send it on to the FindController, you need to become the text field’s delegate and implement the textFieldShouldReturn: method. But before you do that, you need to do one more thing in Interface Builder.

You start by making the RTMasterViewController a UITextFieldDelegate. Update RTMasterViewController.h with the bolded code in Listing 19-1 to have it adopt the UITextFieldDelegate protocol.

Listing 19-1: Updating the RTMasterViewController Interface

#import <UIKit/UIKit.h>

@interface RTMasterViewController : UITableViewController

                                     <UITextFieldDelegate>

@property (strong, nonatomic)

            RTDetailViewController *detailViewController;

@property (weak, nonatomic)

                           IBOutlet UITextField *findText;

@end

The heavy lifting will be done in the TextField’s textFieldShouldReturn: delegate method. The delegate will be passed the Text field being edited as an argument, and the Master View controller (the delegate) will pass that on to the Find controller.

First, you have to update the RTMasterViewController implementation by adding the bolded code in Listing 19-2 to RTMasterViewController.m.

Listing 19-2: Updating the RTMasterViewController Implementation

#import “RTMasterViewController.h”

#import “RTDetailViewController.h”

#import “RTAppDelegate.h”

#import “Trip.h

#import “FindController.h”

@implementation RTMasterViewController

You’ll need to make RTMasterViewController the textField delegate. To do that, add the code in bold in Listing 19-3 to viewDidLoad in RTMasterViewController.m.

Listing 19-3: Make the RTMasterViewController the textField Delegate

- (void)viewDidLoad

{

  [super viewDidLoad];

  RTAppDelegate* appDelegate =

             [[UIApplication sharedApplication] delegate];

  self.title = appDelegate.trip.destinationName;

  UIImageView* imageView = [[UIImageView alloc]

       initWithImage:[appDelegate.trip destinationImage]];

  self.tableView.backgroundView = imageView;

  UISwipeGestureRecognizer *swipeGesture =

  [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeGesture:)];

  swipeGesture.direction =  UISwipeGestureRecognizerDirectionLeft;

  [self.view addGestureRecognizer:swipeGesture];

  self.findText.delegate = self;

}

Now you can implement the textFieldShouldReturn: delegate method by adding the code in Listing 19-4 to RTMasterViewController.m.

Listing 19-4: Implementing textFieldShouldReturn:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {

  [textField resignFirstResponder];

  if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){

    FindController * findController =

     [[UIStoryboard

     storyboardWithName:@”MainStoryboard_iPad” bundle:nil]

         instantiateViewControllerWithIdentifier:@”Find”];

    findController.findLocation = textField.text;

    RTDetailViewController *currentDetailViewController;

    currentDetailViewController =

    [self.splitViewController.viewControllers lastObject];

    if (

      currentDetailViewController.masterPopoverController != nil)

        [currentDetailViewController.

                  masterPopoverController   

                              dismissPopoverAnimated:YES];

    self.splitViewController.delegate = findController;

    findController.popOverButton =  

                currentDetailViewController.popOverButton;

    findController.masterPopoverController =  

       currentDetailViewController.

                                  masterPopoverController;

    NSMutableArray* controllers =

     [NSMutableArray arrayWithObjects:

       (self.splitViewController.viewControllers)[0],  

findController, nil];

    self.splitViewController.viewControllers =

                                              controllers;

  }

  else {

    FindController *findController =

    [[UIStoryboard

      storyboardWithName:@”MainStoryboard_iPhone”

                                               bundle:nil]

         instantiateViewControllerWithIdentifier:@”Find”];  

    findController.findLocation = textField.text;

    [self.navigationController

          pushViewController:findController animated:YES];

  }

  return YES;

}

The first thing Listing 19-4 does for you is to send a message to the Text field asking it to resign as first responder:

[textField resignFirstResponder];

This has the side effect of dismissing the keyboard.

What you do next is another case where what happens depends on whether your app is running on an iPad or iPhone.

If you’re running on an iPad, you instantiate FindController from MainStoryboard_iPad, just as you instantiate the Event Page controller in Chapter 16.

FindController * findController =

  [[UIStoryboard storyboardWithName:

      @”MainStoryboard_iPad” bundle:nil]

         instantiateViewControllerWithIdentifier:@”Find”];

You then assign the text from textField to the FindController findLocation property (which you’ll add shortly to the FindController).

findController.findLocation = textField.text;

You then dismiss the popover if it’s present.

RTDetailViewController *currentDetailViewController;

    currentDetailViewController =

    [self.splitViewController.viewControllers lastObject];

if (currentDetailViewController.masterPopoverController

                                                   != nil)

  [currentDetailViewController.

                  masterPopoverController   

                              dismissPopoverAnimated:YES];

Then you assign the popOverButton and masterPopoverController properties and make FindController the Split View controller delegate.

self.splitViewController.delegate = findController;

    findController.popOverButton =  

                currentDetailViewController.popOverButton;

    findController.masterPopoverController =  

       currentDetailViewController.

                                  masterPopoverController;

Then you simply make FindController the new Detail View controller in the Split View controller’s viewControllers property.

NSMutableArray* controllers =

     [NSMutableArray arrayWithObjects:

       (self.splitViewController.viewControllers)[0],  

                   findController, nil];

self.splitViewController.viewControllers = controllers;

Note that, if it’s an iPhone you’re dealing with, you instantiate the FindController, assign the findLocation property, and push it on the Navigation controller stack, which causes the view to slide into place.

FindController *findController =

[[UIStoryboardstoryboardWithName:

     @”MainStoryboard_iPhone” bundle:nil]

         instantiateViewControllerWithIdentifier:@”Find”];  

    findController.findLocation = textField.text;

    [self.navigationController

          pushViewController:findController animated:YES];

You finally return YES to have the Text field implement its default behavior for the Go-Key-Formerly-Known-As-Return.

If you look back to Chapter 13, this is pretty much the same logic you added to prepareForSegue:sender:. As an exercise, you might want to create a new method that includes the common code.

By the way, you’ll notice some Live Issue errors here. You’ll need to add the findLocation property to the FindController, which you will do in the “Finding the Location” section, later in the chapter.

Disabling cell highlighting

You do have a problem, though. If the user touches outside the label, the cell is automatically highlighted when selected, and it stays selected. The solution is to disable the blue highlight when a cell is selected. To do that, you’ll need to do the following:

1. Select the MainStoryboard in the Project navigator.

2. Select the Master View controller in the Document Outline, open all the disclosure triangles, and select the Table View Cell for Find.

3. In the Table View Cell section of the Attributes inspector, choose None from the Selection pop-up menu.

Now you’re ready to add the FindController methods.

Finding the Location

In Listing 19-3, you assigned the text that the user entered into the Text field to a FindController property. Now you need to update the FindController interface to declare the property by adding the bolded code in Listing 19-5.

Listing 19-5: Updating the FindController Interface

#import “MapController.h”

@interface FindController : MapController

@property (strong, nonatomic) NSString *findLocation;

@end

Next, add the bolded code in Listing 19-6 to the FindController.m file to synthesize it and import the header files of the classes you’ll use.

Listing 19-6: Updating the FindController Implementation

#import “FindController.h”

#import “RTAppDelegate.h”

#import “Trip.h”

#import “Annotation.h”

@implementation FindController

In my discussion of geocoding in Chapter 18, I point out that MapController sends the CLGeocoder the reverseGeocodeLocation: message to convert a coordinate to an address. The idea now is for you to send the geocodeAddressString: message to convert an address or point of interest into a coordinate.

This time, however, you go about it in a very interesting way. When the user enters a location he or she wants to find, you want to create an annotation for that found location and display it on the map. The problem here is that you really don’t want the FindController (or any other controller) to start creating annotations — that’s the Trip model’s job.

Instead, based on the principles of encapsulation and loose coupling, you’ll have the Trip object add the found location as additional model data. Therefore, you want the Trip object to create the Annotation object and send it to the FindController to add as a Map annotation.

Although you could create view controller methods to be used by the Trip object to add the annotation, a more interesting (and better) way is to use the block design pattern. As I say when I introduce you to blocks in Chapter 10, blocks actually could make your code less complex, and here’s an opportunity for you to see why.

Add the code in bold in Listing 19-7 to viewDidLoad in FindController.m.

Listing 19-7: Updating viewDidLoad

- (void)viewDidLoad

{

  [super viewDidLoad];

  self.title = self.findLocation;

  void (^addFindLocationCompletionHandler)

   (Annotation *, NSError *) = ^(Annotation *annotation,

                                          NSError *error){

    if (error!= nil || annotation == nil) {

      NSLog(@”Geocoder Failure! Error code: %u,

        description: %@, and reason: %@”, error.code,

          [error localizedDescription],

                          [error localizedFailureReason]);

    }

    else {

      MKCoordinateRegion region;    

      region.center.latitude =

                           annotation.coordinate.latitude;

      region.center.longitude =

                          annotation.coordinate.longitude;

      region.span.latitudeDelta = .05;

      region.span.longitudeDelta = .05;

      [self.mapView setRegion:region animated:NO];  

      [self.mapView addAnnotation:annotation];

    }

  };

  RTAppDelegate *appDelegate =

             [[UIApplication sharedApplication] delegate];

  [appDelegate.trip addLocation:self.findLocation

      completionHandler:addFindLocationCompletionHandler];

}

You get a compiler error when adding this code, but you fix that by declaring the completion handler method in the Trip interface, which is covered in the following section.

You set the title to the string the user entered:

  self.title = self.findLocation;

You then send the Trip object the addLocation:completionHandler: message. The completion handler is the block you declared. It’s defined in Trip, as you’ll see, as

(void (^)(Annotation *annotation, NSError* error))

That’s a block that has no return value and two parameters, an Annotation and an NSError. The block’s logic is actually quite straightforward. First, you check for an error, as you do in Chapter 18:

if (error!= nil || annotation == nil) {

  NSLog(@”Geocoder Failure! Error code: %u, description:

    %@, and reason: %@”, error.code,

        [error localizedDescription],

                          [error localizedFailureReason]);

}

You’re passed in an Annotation (which will be created in the Trip method addLocation:completionHandler:), which conforms to the MKAnnotation protocol, and you set the region based on the annotation’s coordinates:

MKCoordinateRegion region;    

region.center.latitude = annotation.coordinate.latitude;

region.center.longitude = annotation.coordinate.longitude;

region.span.latitudeDelta = .05;

region.span.longitudeDelta = .05;

[mapView setRegion:region animated:NO];

Next, you add the annotation to the map:

[mapView addAnnotation:annotation];

addAnnotation: is an MKMapView method that adds an object that conforms to the MKAnnotation protocol to the map. This is similar to what you did in MapController when you sent the addAnnotations: message to add an array of annotations. Doing this, as you see on the map, is additive; that is, adding a new annotation doesn’t replace the array you added in MapController.

Next, you add the addLocation:completionHandler: method (the one that will create the Annotation) to Trip.m. But first, you need to update the Trip.m implementation, so add the bolded code in Listing 19-8 to Trip.h.

Listing 19-8: Updating the Trip Interface

#import <Foundation/Foundation.h>

#import <MapKit/MapKit.h>

@class Annotation;

@interface Trip : NSObject

- (UIImage *) destinationImage;

- (NSString *) destinationName;

- (CLLocationCoordinate2D) destinationCoordinate;

- (id)initWithDestinationIndex:(int)destinationIndex;

- (NSString *)weather;

- (int)numberOfEvents;

- (NSString *)getEvent:(int)index;

- (NSArray *)createAnnotations;

- (NSString *)mapTitle;

- (void)addLocation:(NSString *)findLocation

     completionHandler:

      (void (^)(Annotation *annotation, NSError* error))  

                                               completion;

;

@end

This code adds the addLocation:completionHandler: method declaration, and because one of its parameters is an Annotation, you need to add the @class statement as well.

void (^foundLocationCompletionHandler) (Annotation *annotation, NSError* error); is a type just like int or FindController. It’s a block object with the name of foundLocationCompletionHandler that has no return value and two parameters: Annotation and NSError.

I’m going to save FindController’s completion block in the foundLocationCompletionHandler instance variable. I really don’t need to, because, as you’ll see, the addLocation:completionHandler: has access to it as a parameter. I did it this way to show you how to do that. It took me some time to really get my head around blocks and passing them as parameters, and I wanted to show you how easy it is after you see how it’s done. You can use this example as a model for your own apps. I know that it may seem a bit overwhelming now, but I promise you that you’ll find yourself using blocks in this way (as I do) as you gain more experience in your own application development.

Add the foundLocationCompletionHandler instance variable to Trip.m by adding the code in bold in Listing 19-9.

Listing 19-9: Updating the Trip Implementation

#import “Trip.h”

#import “Destination.h”

#import “Events.h”

#import “Annotation.h”

@interface Trip () {  

  NSDictionary *destinationData;

  NSMutableArray *pois;

  Destination* destination;

  Events *events;

  void (^foundLocationCompletionHandler)

                 (Annotation *annotation, NSError* error);

}

Next, add the addLocation:completionHandler: in Listing 19-10 to Trip.m.

Listing 19-10: Adding the addLocation:completionHandler: Method

- (void)addLocation:(NSString *)findLocation             

                                       completionHandler:

    (void (^)(Annotation *annotation, NSError* error))  

                                              completion {

  void (^clGeocodeCompletionHandler)(NSArray *, NSError *) = ^(NSArray *placemarks, NSError *error){

CLPlacemark *placemark = placemarks[0];

    Annotation *foundAnnotation;

    if (error!= nil || placemark == nil) {

      NSLog(@”Geocoder Failure! Error code: %u”,

          error.code);

    }

    else {

      foundAnnotation = [[Annotation alloc]init];

      foundAnnotation.coordinate =  

                            placemark.location.coordinate;

      foundAnnotation.subtitle =

        [NSString stringWithFormat:@“ Lat:%f Lon:%f“, placemark.location.coordinate.latitude,    

                 placemark.location.coordinate.longitude];

if (placemark.areasOfInterest[0]) {

        foundAnnotation.title =

              placemark.areasOfInterest[0];

      }

      else {

        if (placemark.thoroughfare) {

          foundAnnotation.title =

            [NSString stringWithFormat:@“%@ %@“,   

              placemark.subThoroughfare,   

                                  placemark.thoroughfare];

        }

        else {

          if (placemark.locality  ) {

            foundAnnotation.title = placemark.locality;

          }

          else

            foundAnnotation.title =

                                         @“Your location“;

        }

      }

    }

    foundLocationCompletionHandler(

                                  foundAnnotation, error);

  };

  foundLocationCompletionHandler = completion;

  CLGeocoder* geocoder = [[CLGeocoder alloc] init];

    [geocoder geocodeAddressString:findLocation  

            completionHandler:clGeocodeCompletionHandler];

}

First (skipping past the block declarations for a moment), you save a reference to the block that was sent as a parameter:

foundLocationCompletionHandler = completion;

imageYou really don’t need to save a reference to the block here, but I want to illustrate that blocks are treated like any other variable.

Then you allocate and initialize CLGeocoder just as you do in Chapter 18:

CLGeocoder* geocoder = [[CLGeocoder alloc] init];

  [geocoder geocodeAddressString:findLocation completionHandler:clGeocodeCompletionHandler];

This time, however, you send the geocodeAddressString:completionHandler: message after you initialize the CLGeocoder (rather than the reverseGeocodeLocation:location completionHandler: message you sent in Chapter 18).

This message submits a forward-geocoding request using the text the user entered and describes the location you want to look up. For example, you could specify the string “1 Infinite Loop, Cupertino, CA” to locate Apple headquarters.

In the completion block, you check for a successful completion and then create a new Annotation. When you’re done, you call the completion block that the FindController sent you, using the block you had assigned to the foundLocationCompletionHandler variable earlier:

foundLocationCompletionHandler(foundAnnotation, error);

Finally, if you decide that you don’t want the destination and the point of interest annotations you display in the MapController displayed, just override the method that adds the annotations in the MapController superclass:

- (void) addAnnotations {

}

Making the Map Title the Found Location

In Chapter 17, I show you how to add the map title to the toolbar. In that discussion, I add a method named mapTitle, which in the case of the MapController, sends a message to the Trip object to get the map’s title. In the case of the FindController, I want to use the text the user entered as the title, so all I do is override mapTitle. Add the code in Listing 19-11 to FindController.m to display the title as the found location.

Listing 19-11: Override mapTitle

- (NSString *)mapTitle {

  return  self.findLocation;

}

If you build and run RoadTrip, and enter Radio City Music Hall in the Find field in the Master view and tap the annotation, you should see the screen displayed in Figure 19-7. (Notice I did not go down the road of displaying the Destination and the Point of Interest annotations, but even if you did, you’d still see the Radio City Music Hall annotation.)

imageIf you wanted to, you could even create a typedef for the addLocation:completionHandler:. The purpose of typedef is to assign another name to a type whose declaration is unwieldy. You’d want to use a typedef if you were going to have to type void (^foundLocationCompletionHandler) (Annotation *annotation, NSError* error); more than once. And while that is not true for RoadTrip, I wanted to show you how to create a typedef for a block that you could use in your own projects. Type the following code:

image

Figure 19-7: Skippy loves the Rockettes.

typedef void (^addLocationCompletionHandler)

               (Annotation *annotation, NSError* error);

Add the instance variable as

addLocationCompletionHandler

                         addFindLocationCompletionHandler;

and then save the block in the new instance variable

addFindLocationCompletionHandler = completion;

and (finally) call the block in this way instead:

addFindLocationCompletionHandler(foundAnnotation, error);

Adding the FindController to the iPhone Storyboard

The iPhone storyboard, fortunately, uses the same Objective-C FindController class that you just defined for the iPad storyboard. But you still have some work to do, because you need to add a FindController scene to your iPhone storyboard.

One approach is to copy the Find scene from the iPad storyboard file and paste it into the iPhone storyboard file. This will work, but the iPad version has the Toolbar at the top of the view, which is the right answer for the iPad, but not for the iPhone.

You should rearrange the view elements in the iPhone version by dragging the toolbar to the bottom of the view and moving the Map view to the top of the enclosing view. Be sure to adjust the location of the iPhone toolbar in the Size inspector, as shown in Figure 19-8.

image

Figure 19-8: Adjusting the toolbar in the FindController scene in the iPhone storyboard.

The Find operation in the iPhone looks as shown in Figure 19-9.

image

Figure 19-9: The Find function on the iPhone.