Sign up for a Parse account to implement this tutorial and more!

Sign Up

Anywallicon
Anywall

Explore the use of PFGeoPoints and PFUser in a real application. Anywall is a geocaching app with a social twist. You'll learn everything from implementing a basic user management workflow to tracking gps location with CoreLocation.

Anywall
iOS
PFUser
PFGeoPoint
PFObject
PFQuery
PFQueryTableViewController
MapKit
CoreLocation

Download code for this tutorial:

.zip File GitHub

Anywall is a geocaching application that allows users to share messages by placing them in real world locations. The application is available on the app store, and is meant to give you an overview of a real app that uses Parse as a backend. In this tutorial, we'll not only look at all of the Parse functionality used, but also many concepts surrounding the uses of Parse. In Anywall, users log in and are able to see the closest posts both on a map and in a table. To view the content of a particular post, a user must be within a certain radius from the location it was dropped.

This tutorial is divided into five parts that each focus on a different aspect of Anywall. Feel free to read them in any order, but we recommend that you download the source code and refer to it while reading. Some parts of the code were omitted or changed in this tutorial in order to keep it clear and concise, so you may find it useful to keep the full version close at hand.

1. Anywall Overview
Get familiar with Anywall's architecture. This section will explain the role of all the view controller in Anywall and give you an overview of the NSNotificationCenter which is used throughout the app.

2. User Management
Learn how to create a user login and registration workflow from the user interface to the PFUser request. This section will explore the workings of Anywall's user management and show you how to create a similar workflow in your own applications.

3. Adding New Posts
Adding new wall posts is at the front and center of Anywall's feature repertoire. Here you will learn how to create PFObjects with PFGeoPoints and how the PAWWallPostCreateViewController interacts with other classes while maintaining good modularity.

4. MapKit and PFGeoPoint
Dive headfirst into geocaching by learning all about CoreLocation and MapKit. This section focuses on exploring the PAWWallViewController and understanding how the user's location is used to make Parse queries and how the wall posts are displayed on a map.

5. PFQueryTableViewController and PFGeoPoints
The map view is only half the story. Anywall also displays posts inside a table view. This section will examine how the PAWWallTableViewController makes use of the PFQueryTableViewController to mimic the posts displayed in the map view.

1. Anywall Overview

1.1. Architecture

Anywall consists of seven view controllers. The PAWWelcomeViewController is the initial screen that is presented and it allows a user to either log in or register through the PAWLoginViewController and the PAWNewUserViewController respectively. These view controllers are covered in Section 2 of this tutorial.

Once a user is logged in, the PAWWallViewController is displayed. As we'll see in Section 4 this view controller displays nearby posts in MKMapView. This same screen also displays the child view controller PAWWallPostsTableViewController. As we will see in Section 5, this view controller is a subclass of PFQueryTableViewController, mimics the data displayed on the map.

The PAWWallPostCreateViewController and the PAWSettingsViewController are both presented from the PAWWallViewController's navigation bar buttons. The first allows a user to add a new wall post and the second allows a user to change the search radius and to log out. The PAWWallPostCreateViewController is discussed in Section 3.

Each of these view controllers are supported by a xib file which you can find in the “View Controllers > XIB Resources” folder of the Xcode project.

1.2. The NSNotificationCenter

Sometimes your application's view controllers need to communicate to each other. To accomplish this, Anywall uses the NSNotificationCenter to broadcast information throughout the app. Take the user’s location for example. When CoreLocation informs a view controller that the user’s location has changed, it might need to share this information with other parts of the app. The map view will need to run a new query to obtain more points, the table view will need to update its displayed points as well and maybe this information will be needed by a new part of the app that we will add later. Instead of coupling all these different parts of the application, we can simply post a new notification. All objects currently listening for the notification will be notified and will be able to perform whatever task is needed. The following code snippets outline the use of the notification center for the above example in Anywall.

First in the app delegate we declare a constant string that will be used to represent this notification. Additionally, we have also decided to provide a method in the app delegate that can be called in order to post a new notification.

// AppDelegate.m

// In the app delegate we create a constant string to be used as an event name
static NSString* const kPAWLocationChangeNotification= @"kPAWLocationChangeNotification";

// We also add a method to be called when the location changes.
// This is where we post the notification to all observers.
- (void)setCurrentLocation:(CLLocation *)aCurrentLocation 
{
    NSDictionary *userInfo = [NSDictionary dictionaryWithObject: aCurrentLocation
                                                         forKey:@"location"];
    [[NSNotificationCenter defaultCenter] postNotificationName: kPAWLocationChangeNotification 
                                                        object:nil 
                                                      userInfo:userInfo];
}

Now we need to call the setCurrentLocation: method in the app delegate when we learn that the user's location has changed. We use one of the CLLocationManager's delegate method to know when the location changes and call the setCurrentLocation to notify all observers. We'll talk more about the CLLocationManager in Section 4, but for now know that we use the PAWWallViewController as the delegate.

// PAWWallViewController.m

// The CoreLocation object CLLocationManager, has a delegate method that is called  
// when the location changes. This is where we will post the notification
- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation 
{
    PAWAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    
    // This is where the post happens
    [appDelegate setCurrentLocation:newLocation]; 
}

Finally, in the viewDidLoad method, our PAWWallPostsTableViewController can register itself as an observer of the notification, and specify that the locationDidChange: selector should be called as a result.

// PAWWallPostsTableViewController.m

// The view controller requests to be an observer of 
// the notification kPAWLocationChangeNotification
- (void)viewDidLoad {
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(locationDidChange:) 
                             name:kPAWLocationChangeNotification
                           object:nil];
}

// When the kPAWLocationChangeNotification is posted, the selector specified in 
// the addObserver: method is called and we can update the display
- (void)locationDidChange:(NSNotification *)note {
    // Update the table with the new points
}

As good programmers, we also remember to clean up everything by removing the the PAWWallPostsTableViewController from the list of observers using the dealloc and viewDidUnload methods. Remember that the confusingly named viewDidUnload method is used to handle a low memory warning. By also implementing the dealloc method, we ensure that the list of observers is also cleaned up when the view controller is deleted.

// Finally, when the view controller no longer needs to observe the
// notification, it removes itself
- (void)viewDidUnload {
    [[NSNotificationCenter defaultCenter] removeObserver:self    
                                                    name:kPAWLocationChangeNotification  
                                                  object:nil];
}

The NSNotificationCenter is a powerful tool that can help to keep your application modular and decoupled. Unfortunately, it does come at a price. As you can see from the above code, it can quickly become difficult to follow what’s going on if it is over used. For this reason, it is strongly recommended that you use the notification center sparingly and that you properly document its use in your application.

Anywall uses the notification center to broadcast three types of notification. The first is the one we just saw, the kPAWLocationChangeNotification sent when a user changes location. The other two are the kPAWFilterDistanceChangeNotification, sent when the search radius distance is changed, and the kPAWPostCreatedNotification, sent when the user creates a new post. All three of these notifications are observed by the PAWWallViewController and the PAWWallTableViewController. As mentioned above, the PAWWallViewController in charge of displaying all of the nearby wall posts in a map view, and the PAWWallTableViewController is used to display these same wall posts in a table view. Each of these notification implies that the data currently displayed needs to be changed, so the two view controllers use them to update their data.

The NSNotificationCenter is also used for one other reason in Anywall. While unfortunately confusing, this use of the notification center is not related to the above discussion. Take a look at the PAWLoginViewController. When either of the UITextFields are empty, we want to disable the “Done” button. To accomplish this, a developer familiar with the Cocoa-Touch API might suggest using a UITextField delegate method called when the text is changed. Unfortunately, the UITextFieldDelegate protocol does not provide such a granular level of control. To accomplish our goal we can add the view controller as an observer of the UITextFieldTextDidChangeNotification through the NSNotificationCenter. The flow of information is the same as discussed above, but here the notification center is used as a workaround not as a way to improve or app’s architecture. Keep in mind that this approach is used in the PAWLoginViewController, PAWNewUserViewController and PAWCreateWallPostViewController.

1.3. Wrapping Up

In this first section of the tutorial we got a broad overview of the application's calsses and focused in on how the different view controllers interact via the NSNotificationCenter. In the next section, we'll start our dissection of the application by looking at PFUsers and the user management workflow.

2. User Management

The notion of users is at the core of social applications like Anywall. Using the PFUser object makes it much easier to manage users, but the client-side workflow surrounding registration and login can be a challenge. This section of the tutorial will shed some light on good practices for implementing the user management workflow and will help you avoid some of the common pitfalls. We'll start with a discussion of some user interface challenges. We'll then take a look at the code needed to login and create new users using the PFUser object, and we'll finish by looking at how we can take advantage of the automatic caching capabilities of the PFUser object.

The PAWLoginViewController and the PAWNewUserViewController are used for logging in and registering users. We will be discussing the implementation of these two view controllers simultaneously since their implementations are very similar.

2.1. From UI to Data

The user interface elements of note are the UITextFields, used to capture username and password information, and the "Done" UINavigationBarButton, that allows the user to submit the entered information. Since it is required that all fields be completed, we only enable the "Done" button when there is text in all of the UITextFields. To accomplish this, every time the user enters a new character, we check if all fields have text, and enable or disable the "Done" button accordingly. Since UITextField does not have a delegate method called every time the user inputs a new character, we need to use the NSNotificationCenter. If you are not familiar with this object, take a look at Section 1.2 for a brief overview.

We begin by registering the view controller as an observer of the UITextFieldTextDidChangeNotification for each of our text fields.

[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(textInputChanged:) 
                                             name:UITextFieldTextDidChangeNotification     
                                           object:usernameField];
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(textInputChanged:) 
                                             name:UITextFieldTextDidChangeNotification 
                                           object:passwordField];
// For the registration view controller only
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(textInputChanged:) 
                                             name:UITextFieldTextDidChangeNotification 
                                           object:passwordAgainField];

Notice that we set the textInputChanged: selector as the responder of the notification. When the notification is triggered by the UITextField object, this method will called. The implementation of textInputChanged: is fairly straightforward. We check if all fields contain text, and only set the "Done" button to the enabled state if they do.

- (void)textInputChanged:(NSNotification *)note;
    BOOL enableDoneButton = NO;
    // Check if all the text fields are populated
    // The registration view controller would also check the passwordAgainField
    if (usernameField.text != nil && usernameField.text.length > 0 
        && passwordField.text != nil && passwordField.text.length > 0) 
    {
        enableDoneButton = YES;
    }
    doneButton.enabled = enableDoneButton; // Set the done button accordingly
}

The last detail to note about the user interface concerns the keyboard. When a user taps on a UITextField, it becomes the first responder. This causes the keyboard to appear and the text field to gain focus. Since we want the keyboard to be dismissed when the "Done" button is selected, we call the resignFirstResponder: method on each of the text fields. The one currently in focus (the first responder) will resign its role and the keyboard will disappear. This technique can be used in a variety of situations where you want to manually dismiss the keyboard.

- (IBAction)done:(id)sender {
    // Dismiss the keyboard
    [usernameField resignFirstResponder]; 
    [passwordField resignFirstResponder];
    [passwordAgainField resignFirstResponder]; // Only for the registration vc
    ...
}

2.2. Validating the Data Locally

Now that we have an elegant way of guiding the user through our interface, we'll take a look at how to extract the information from the text fields. To access the entered information, we use the text property of the UITextFields. It's a good idea to do some local validation of the data to save us from doing unnecessary server requests. In Anywall, we'll simply ensure that the text fields were indeed filled out. Note that we mitigated this scenario by disabling the "Done" button when the text fields were empty. However, we shouldn't rely solely on UI elements to validate our data.

- (IBAction)done:(id)sender {
    ...
    // First we extract the text from our text fields
    NSString *username = usernameField.text;
    NSString *password = passwordField.text;
    NSString *passwordAgain = passwordAgainField.text; // Only for registration
Anywall
    // Next, we check if the text fields were properly filled out
    if (username.length == 0 
     || password.length == 0
     || passwordAgain.length == 0) // Only for registration
    {
        // Uh oh, better tell the user to complete the form!
    }
    ...
}

The above is all the validation we do for the login view controller. In the case of the registration view controller, PAWNewUserViewController, we also want to ensure that the password confirmation text field, passwordAgainField, matches the original password field. We do this by performing another check after the above code.

- (IBAction)done:(id)sender {
    ...
    // Remember, passwordAgain is the content of passwordAgainField.text
    if ([password compare:passwordAgain] != NSOrderedSame) 
    {
        // Uh oh, looks like the passwords didn't match!
    }
    ...
}

When errors like these happen, Anywall handles it by setting the erroneous text field as the first responder (thus giving it focus and displaying the keyboard) and by displaying a UIAlertView to inform the user of the problem. You can take a look at the full code to see exactly how to implement this type of error handling.

2.3. Registering and Logging-in a PFUser

Now that we know our data is plausible, we can make our server request using the PFUser object. Let's begin with the registration of a new user in the PAWNewUserViewController. We create a new PFUser object and set the username and password properties to the values extracted from the UITextFields. We then use the signUpInBackgroundWithBlock: method to register the user and handle the response from the server. Handling the response from the server can be a little more challenging, so take a look at the code first and we'll go through it step by step.

- (IBAction)done:(id)sender {
   ...
   PFUser *user = [PFUser user];
   user.username = username;
   user.password = password;
   [user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) 
   {
      if (error) // Something went wrong
      { 
         // Display an alert view to show the error message
         UIAlertView *alertView = 
         [[UIAlertView alloc] initWithTitle:[[error userInfo] objectForKey:@"error"] 
                                    message:nil 
                                   delegate:self
                          cancelButtonTitle:nil 
                          otherButtonTitles:@"Ok", nil];
         [alertView show];
         
         // Bring the keyboard back up, user will probably need to change something
         [usernameField becomeFirstResponder];
         return;
      }

      // Success!
      // We push the next view on the navigation stack
      PAWWallViewController *wallViewController = 
         [[PAWWallViewController alloc] initWithNibName:nil bundle:nil];
      UINavigationController *navController = (UINavigationController *)self.presentingViewController;
      [navController pushViewController:wallViewController animated:NO];

      // And since this view controller was presented modally, we dismiss ourself
      [self dismissModalViewControllerAnimated:YES];
   }];
}

The block parameter of this function is called when the application receives a reply from the Parse server. We start by checking the error parameter of the block to see if the request failed. In the case of an error we create a UIAlertView object and populate its title with the error message received from Parse. To display the alert view on the screen, we simply call its show method. Since it is likely that the user will need to modify some of the entered information after an error, we give tell the username text field to become first responder. This will cause this UITextField to gain focus and for the keyboard to be displayed.

If there is no failures, we can display the main view controller of the app, the PAWWallViewController. Since the login and registration view controllers are presented modally on top of the welcome view controller, they are not part of the navigation stack. This means that to display the PAWWallViewController, the login and registration view controllers need to first push it on the navigation controller of the welcome screen, and then dismiss themselves using the dismissModalViewControllerAnimated: method.

The process for logging in a user is very similar. We start by calling the PFUser's logInWithUsernameInBackground: password: block: method with the username and password values we extracted earlier, and then handling the response in a block. The error handling is done a little differently here so like last time, take a look at the code and we'll go through it step by step.

- (IBAction)done:(id)sender {
   ...
   [PFUser logInWithUsernameInBackground:username 
                                password:password 
                                   block:^(PFUser *user, NSError *error) 
   {
      if (user) // Login successful
      {
         // Create next view controller to show
         PAWWallViewController *wallViewController = 
            [[PAWWallViewController alloc] initWithNibName:nil bundle:nil];
         // Push it on the welcome screen's the navigation controller
         UINavigationController *navC = (UINavigationController *)self.presentingViewController;
         [navC pushViewController:wallViewController animated:NO];
         // Dismiss this view controller
         [self dismissModalViewControllerAnimated:YES];
      } 
      else // Login failed
      {
         UIAlertView *alertView = nil;
   
         if (error == nil) // Login failed because of an invalid username and password
         {
            // Create an alert view to tell the user
            alertView = [[UIAlertView alloc] initWithTitle:@"Couldn't log in:" 
                                      "\nThe username or password were wrong." 
                                           message:nil 
                                          delegate:self 
                                 cancelButtonTitle:nil 
                                 otherButtonTitles:@"Ok", nil];
         } 
         else // Login failed for another reason
         {
            // Create an alert view to tell the user
            alertView = [[UIAlertView alloc] initWithTitle:[[error userInfo] objectForKey:@"error"]
                                           message:nil 
                                          delegate:self 
                                 cancelButtonTitle:nil 
                                 otherButtonTitles:@"Ok", nil];
         }
         // Show the alert view
         [alertView show];

         // Bring the keyboard back up, user will probably need to change something
         [usernameField becomeFirstResponder];
      }
   }];
}

Unlike the registration view controller, we start with the successful case by checking if a user object is returned. If there is, we use the same code to display the PAWWallViewController. Note that since the Parse framework takes care of keeping a copy of the logged in user, we don't have to worry about saving it. It can always be accessed with the [PFUser currentUser] method.

If a nil user object is returned to the block, we have a failed request. The most common reason for a failure here is an incorrect password and username combination. In such a case, the error parameter will also be nil. In our code, we check for a nil error object (if we get to this point we also have a nil user object) and report the failure using a UIAlertView. If the error object is not nil, then we have different error (like a missing internet connection for example). In such a case, we the error message obtained in a UIAlertView.

2.4. Keeping the user Logged-in

The last piece of the user management puzzle is caching. It would be frustrating for a user to login every time they open Anywall. That is why Parse automatically caches the PFUser object when a user logs in. We can use the app delegate's application:didFinishLaunchingWithOptions: method to check if the cache contains a user, and if there is, jump directly to the map view controller.

- (BOOL)application:(UIApplication *)application 
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   ...
   // If we have a cached user, we'll get it back here
   PFUser *currentUser = [PFUser currentUser];
   if (currentUser) 
   {
      // A user was cached, so skip straight to the main view
      PAWWallViewController *wallViewController = 
         [[PAWWallViewController alloc] initWithNibName:nil bundle:nil];

      UINavigationController *navController = 
         [[UINavigationController alloc] initWithRootViewController:wallViewController];
      navController.navigationBarHidden = NO;

      self.viewController = navController;
      self.window.rootViewController = self.viewController;
   } 
   else 
   {
      // No cached user so just present the welcome screen
      PAWWelcomeViewController *welcomeViewController = 
         [[PAWWelcomeViewController alloc] initWithNibName:@"PAWWelcomeViewController" 
                                                    bundle:nil];
      welcomeViewController.title = @"Welcome to Anywall";

      UINavigationController *navController = 
        [[UINavigationController alloc] initWithRootViewController:welcomeViewController];
      navController.navigationBarHidden = YES;

      self.viewController = navController;
      self.window.rootViewController = self.viewController;
   }
}

We start by accessing the cached user using the [PFUser currentUser] method. If the value returned by this method is not nil, then we can assume that the app has a valid user session. To jump directly to the PAWWallViewController, we instantiate it, and set it as the root of our navigation controller. If there is no cached user, then we follow the same procedure, but we set the PAWWelcomeViewController as the root of the navigation controller instead.

2.4. Wrapping Up

In this section of the Anywall tutorial we took a look at the implementation of the user management workflow. We started by looking at strategies to improve the UI by enabling and disabling controls according to user input. Next, we ensured that the form data was plausible by validating it locally. We then looked at logging in and registering users using Parse, and finally, we examined how to take advantage of the automatic caching capabilities of PFUser.

3. Adding New Posts

Now that we have a good grasp on the inner workings on Anywall's user management, we can dive into the core of the app: PFGeoPoints. This class is a sibling of PFObject that we can use to represent geographic locations. Before jumping into MapKit and CoreLocation in Section 4, we'll see how to create and store PFGeoPoints by exploring Anywall's PAWCreatePostViewController. As mentionned in Section 1.1, this view controller is in charge of getting the text input for a new wall post and saving the data in Parse. After the data is saftely stored in Parse, we'll take a quick detour to note how theNSNotificationCenter is used to refresh the app's map and table view.

3.1. Creating a PFObject using a PFGeoPoint

We'll focus our efforts on the implementation of the "Post" navigation bar button touch handler method aptly named postPost:. After a user enters his post message in the text field, this button is in charge of neatly packaging everything in a PFObject and saving it to Parse. The rest of the PAWCreatePostViewController implementation focuses on the character counter and other UI constructs. We won't conver those in this section but take a look at Section 1.2 and the full code for details on how these work.

But let's not get ahead of ourselves. Before creating all the cool Parse objects, there are a few tasks that need our attention. As we saw in Section 2.1 to dismiss the keyboard, we need to remove the focus from a text input control by calling it's resignFirstResponder method. There's one more effect of losing the role of first responder that did not apply in the user managment section. If there is an auto-correct suggestion currently being shown, it will not be inserted until the text input control loses the first responder role. This can be annoying at times, but since it is part of Apple's apps, it is expected from users. So we start by calling this method on our UITextView, and then extract all the information we will need to create our PFObject. Take a look at the code and we'll talk about the details.

- (IBAction)postPost:(id)sender;
{
   // Dismiss keyboard and capture any auto-correct
   [textView resignFirstResponder];

   // Get user's current location
   PAWAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
   CLLocationCoordinate2D currentCoordinate = appDelegate.currentLocation.coordinate;
   
   // Get the post's message
   NSString *postMessage = textView.text;
   
   //Get the currently logged in PFUser
   PFUser *user = [PFUser currentUser];
   ...
}

Both the post message and the PFUser are fairly run-of-the-mill. The user's inputted message is obtained by accessing the text property of the UITextView and the current PFUser is obtained by using the static method currentUser. The user's location is a little more difficult to understand. As we'll see in the Section 4.2, we use the app delegate as a singleton for the user location. It is called a singleton because there only one instance of the object created over the life of the app. Since the app delegate can be accessed from any other class using the [[UIApplication sharedApplication] delegate] method, we can use it to store and retreive the user's most recent location. Section 4.2 will investigate how the user location is obtained and stored there. For now, just assume it is available and ready to use. We access it through the currentLocation property we created in the app delegate and store it in a variable of type CLLocationCoordinate2D. This is a CoreLocation data type that we can use to represent graphical locations. Notice that it is not an object, hence the lack the pointer symbol *. It is in fact a C structure containing two doubles: latitude and longitude.

We can now create our Parse objects. We'll start by creating our PFGeoPoint using the values from our CLLocationCoordinate2D, then package all our data in a PFObject of type "Post" and finally set a read-only access control list on our new PFObject.

- (IBAction)postPost:(id)sender;
{
   ...
   // Create a PFGeoPoint using the user's location
   PFGeoPoint *currentPoint = [PFGeoPoint geoPointWithLatitude:currentCoordinate.latitude 
                                               longitude:currentCoordinate.longitude];

   // Create a PFObject using the Post class and set the values we extracted above
   PFObject *postObject = [PFObject objectWithClassName:kPAWParsePostsClassKey];
   [postObject setObject:postMessage forKey:kPAWParseTextKey];
   [postObject setObject:user forKey:kPAWParseUserKey];
   [postObject setObject:currentPoint forKey:kPAWParseLocationKey];
   
   // Set the access control list on the postObject to restrict future modifications 
   // to this object
   PFACL *readOnlyACL = [PFACL ACL];
   [readOnlyACL setPublicReadAccess:YES]; // Create read-only permissions
   [readOnlyACL setPublicWriteAccess:NO];
   [postObject setACL:readOnlyACL]; // Set the permissions on the postObject
   ...
}

Notice that we use string constants as the values for the keys in postObject (such as kPAWParsePostsClassKey). These constants are all defined in the app delegate and help us avoid typing mistakes.

The last step is to save our PFObject and dismiss the view controller to return the user to the main view. Take a look.

- (IBAction)postPost:(id)sender;
{
   ...
   [postObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) 
   {
      if (error) // Failed to save, show an alert view with the error message
      {
         UIAlertView *alertView = 
            [[UIAlertView alloc] initWithTitle:[[error userInfo] objectForKey:@"error"] 
                                       message:nil 
                                      delegate:self 
                             cancelButtonTitle:nil 
                             otherButtonTitles:@"Ok", nil];
         [alertView show];
         return;
      }
      if (succeeded) // Successfully saved, post a notification to tell other view controllers
      {
         dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:kPAWPostCreatedNotification 
                                                                object:nil];
         });
      } 
   }];

   // Dismiss this view controller and return to the map view
   [self dismissModalViewControllerAnimated:YES];
}

We use saveInBackgroundWithBlock: to do the network call asynchronously and use a block to handle the reply from the server. If the save fails, we show the user the error message in a UIAlertView. If the save is successful, we dispatch a notification to the NSNotificationCenter. In the next section, we'll see why this notification is important.

3.2. Refreshing After Data Creation

When the user creates a new wall post, it will not appear on the map view or in the table view until a manual refresh is triggered. Since we know new data is available, it would be good idea to trigger a refresh automatically. But how can the PAWCreatePostViewController communicate with all of the other view controllers that may need to refresh their data? The answer is to use the NSNotificaitonCenter. As we discussed in Section 1.1, we can use this object to broadcast notifications throughout the app. Let's look a bit more deeply into how it is used in this particular case.

As seen above, the notification is broadcasted after a new wall post is successfully save. The dispatch_async() method is simply used to make sure the current method terminates immediately and that the notification is sent and received on the main thread (regardless of which thread runs the block). Don't worry if you don't understand this, the important line is the one where the notification is sent.

// PAWCreateWallPostViewController.m, in (IBAction)postPost:(id)sender
 
// We make sure it is called on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
   // Post the notification
   [[NSNotificationCenter defaultCenter] postNotificationName:kPAWPostCreatedNotification 
                                                       object:nil];
}

In the current implementation of Anywall, there are two view controller that need to refresh their data when a new wall post is created, the PAWWallViewController and the PAWWallPostsTableViewController. In their viewDidLoad methods, these two view controllers ask to be notified about the kPAWPostCreatedNotification as follows.

// PAWWallViewController.m and PAWWallPostsTableViewController

[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(postWasCreated:) 
                                             name:kPAWPostCreatedNotification 
                                           object:nil];

In their respective implementation of the postWasCreated: method, the two view controllers refresh their data by repeating their query to Parse. We'll take a look at the specific implementation of these queries in the next section.

3.3. Wrapping Up

This section introduced some of the concepts surrounding MapKit and CoreLocation but focused on the creation of PFGeoPoints storing them in your Parse app using a PFObjects. In the next section, we'll move our attention to the biggest class of the app: PAWWallViewController and really explore how MapKit and CoreLocation tie into the Parse framework.

4. MapKit and PFGeoPoint

Apple provides developers with two powerful frameworks for manipulating map views on screen and for interacting with the GPS hardware: MapKit and CoreLocation. This section of the tutorial will cover these two libraries in enough depth to understand how they are used in Anywall and how Parse fits in to provide the needed backend capabilities. The PAWWallViewController will be our main focus during this discussion since it contains the majority of the relevant implementation. As we saw in Section 1.1, this view controller is in charge of tracking the user's location and displaying nearby wall posts on a map.

We'll take a bottom-up approach to the exploration of this view controller. Our first step will be understanding how the wall posts are queried from Parse, and then we'll move up the chain to look at when and where to perform these queries.

4.1. PFGeoPoint based PFQuery

One of the key methods of the PAWWallViewController is the queryForAllPostsNearLocation: withNearbyDistance method. This is the method that performs the query to Parse asking for nearby wall post objects. In the next few paragraphs, we'll learn not only how such a query can be created but also what to do with the results. As we will see, it is not trivial to merge the new query's wall posts with the ones on display.

To begin, we create a PFQuery that will return data based on a geographical property, using whereKey:nearGeoPoint:withinKilometers method. This query is completely based on PFGeoPoints. We first specify the key for a field in our PFObject that represents a PFGeoPoint. In our case, we use the location of a wall post. Next, we specify a given location that will be used as the origin of the radius as well as the distance of this radius. In this application, we use the user's location and an arbitrairily large distance of 100km. Since we also limit the number of objects returned to 20, we do not have to worry about this distance causing an exorbitant amount of posts being returned. We will also hide the message for wall posts that are returned but outside of the search radius. This means the user will need to get closer to certain objects in order to see their message.

We also use the includeKey: method to ask Parse to return us the PFUser objects associated with each wall post objects. This will allow us to display the name of the poster along with the message.

// PAWWallViewController.m

- (void)queryForAllPostsNearLocation:(CLLocation *)currentLocation 
                  withNearbyDistance:(CLLocationAccuracy)nearbyDistance 
{
   PFQuery *wallPostQuery = [PFQuery queryWithClassName:self.className];

   // If no objects are loaded in memory, we look to the cache first to fill the table
   // and then subsequently do a query against the network.
   if ([self.allPosts count] == 0) 
   {
      wallPostQuery.cachePolicy = kPFCachePolicyCacheThenNetwork;
   }
   
   // Create a PFGeoPoint using the current location (to use in our query)
   PFGeoPoint *userLocation = 
         [PFGeoPoint geoPointWithLatitude:currentLocation.coordinate.latitude 
                                longitude:currentLocation.coordinate.longitude];

   // Create a PFQuery asking for all wall posts 100km of the user
   // We won't be showing all of the posts returned, 100km is our buffer
   [wallPostQuery whereKey:kPAWParseLocationKey 
              nearGeoPoint:userLocation 
          withinKilometers:kPAWWallPostMaximumSearchDistance];

   // Include the associated PFUser objects in the returned data
   [wallPostQuery includeKey:kPAWParseUserKey];

   // Limit the number of wall posts returned to 20
   wallPostQuery.limit = [NSNumber numberWithInt:kPAWWallPostsSearch];
   ...
}

The last thing to note in this first part of the method is the cachePolicy. By setting this to kPFCachePolicyCacheThenNetwork, we ask the Parse framework to run the query twice, once on the local cache, and once on the server. This allows us to quickly populate the map with points before making the longer network call.

Now that our query is created, it is time to run it. We use the asynchronous version of the findObjects method and handle the reply in a completion block.

// PAWWallViewController.m

- (void)queryForAllPostsNearLocation:(CLLocation *)currentLocation 
                  withNearbyDistance:(CLLocationAccuracy)nearbyDistance 
{
   ...
   //Run the query in background with completion block
   [wallPostQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) 
   {
      if (error) // The query failed
      {
         NSLog(@"Error in geo query!"); 
      } 
      else // The query is successful
      {
         // 1. Find new posts (those that we did not already have)
         // 2. Find posts to remove (those we have but that we did not get from this query)
         // 3. Configure the new posts
         // 4. Remove the old posts and add the new posts
   ...
}

It is in this block that we need to look at all the objects returned and decide which need to be added to the map and which need to be removed. We could simply remove everything and add all of the results from the query. However, this would not allow us to present the new posts on the map with a pin dropping animation as the pins would be continually animating. We want to animate only the posts that are really new, discretly remove posts that were not returned in the query, and leave all the other ones as-is. To help us understand this, take a look at the following diagram.

Diagram of the posts sets

The posts we just received from the query are in the objects array (blue). The ones currently being displayed are in a property called allPosts (red). We'll begin by finding the posts that were returned by the query, but that are not currently being displayed - the newPosts array. Next, we'll find all posts currently being displayed but that were not returned in the query - the postsToRemove array. Armed with that knowledge we'll able to elegantly modify posts being dislayed. Take a look at the first step, the generation of the newPosts array.

// 1. Find new posts (those that we did not already have)

// In this array we'll store the posts returned by the query
NSMutableArray *newPosts = [[NSMutableArray alloc] initWithCapacity:kPAWWallPostsSearch];
         
// Loop through all returned PFObjects
for (PFObject *object in objects) 
{
   // Create an object of type PAWPost with the PFObject
   PAWPost *newPost = [[PAWPost alloc] initWithPFObject:object];
            
   // Now we check if we already had this wall post
   BOOL found = NO;
   for (PAWPost *currentPost in allPosts) // Loop through all the wall posts we have
   {
      if ([newPost equalToPost:currentPost]) // Are they the same?
      {
         found = YES;
      }
   }

   if (!found) // If we did not already have this wall post
   {
      [newPosts addObject:newPost];
   }
}

After creating our empty newPosts array, we loop through all of the returned objects, and compare each one to each object in our allPosts array. Remember that allPosts is a property that is used to hold all of the posts currently being displayed. If a received post is not equivalent to any of the posts we currenlty have, then we know it is new. Don't worry about the PAWPost class used in the above code. It is an implementation of MKAnnotation, a MapKit protocol. We will discuss this class in the next section. For now, know that it is the class we use to represent entities on the map.

The next step is the postsToRemove array. Take a look the code for this step, and try to notice the similarities with the step 1. Can you tell what we changed here and why?

// 2. Find old posts (those we have but that we did not get from this query)

// Will contain wall posts we currently have that were not returned in the query
NSMutableArray *postsToRemove = 
      [[NSMutableArray alloc] initWithCapacity:kPAWWallPostsSearch];
         
// Loop through all the the wall posts we currently have
for (PAWPost *currentPost in allPosts) 
{
   BOOL found = NO;

   // Loop through all the wall posts we received
   for (PFObject *object in objects) 
   {
     // Create an object of type PAWPost with the PFObject
      PAWPost *newPost = [[PAWPost alloc] initWithPFObject:object];
      if ([currentPost equalToPost:newPost]) // Are they equal?
      {
         found = YES;
      }
   }
            
   // If we did not find the wall post we currently have in the set of posts
   // the query returned, then we add it to the 'postsToRemove' array
   if (!found) 
   {
      [postsToRemove addObject:currentPost];
   }
}

Just like last time, we have two nested loops to compare the same two arrays. The difference is that we are now comparing items from the allPosts array with those in the objects array instead of the other way around. This means that we'll find posts that are currently being displayed but that are not in the results returned by the query. These represent stale posts that we no longer want to show, so we add them to the postsToRemove array.

Before unleashing our two arrays on the map, we need to configure a few details on the set of new posts. Since we want to hide the message for posts located outside our current search radius, we need to loop through all of them, and compare their position to the user's current position. Remember that we created the PFQuery with a radius ok 100km, a distance much greater than our maximum 2.5 miles search radius. So it is likely that many post messages will be hidden.

// 3. Configure the new wall post to display the proper message. If it is outside
//    the search radius, we hide the post message

// We loop through all the new posts (i.e. all objects in the 'newPosts' array)
for (PAWPost *newPost in newPosts) 
{
   // Get the location of the wall post
   CLLocation *objectLocation = 
         [[CLLocation alloc] initWithLatitude:newPost.coordinate.latitude 
                                    longitude:newPost.coordinate.longitude];

   // For posts outside the search radius, we show a different 
   // message by setting the following property
   CLLocationDistance distanceFromCurrent = [currentLocation distanceFromLocation:objectLocation];
   [newPost setTitleAndSubtitleOutsideDistance:( distanceFromCurrent > nearbyDistance ? YES : NO )];

   // Animate all pins after the initial load
   newPost.animatesDrop = mapPinsPlaced;
}

We are now ready to apply our brand new results to the map. Notice that we make the changes to both the map and the allPosts array. This means that after the method finishes, our allPosts array still contains the most up-to-date information, and is ready for the next query.

// 4. Remove the old posts and add the new posts

// We remove all undesired posts from both the cache and the map
[mapView removeAnnotations:postsToRemove];
[allPosts removeObjectsInArray:postsToRemove];
         
// We add all new posts to both the cache and the map
[mapView addAnnotations:newPosts];
[allPosts addObjectsFromArray:newPosts];

Take a second to digest all this information. In this part of Section 4, we saw how to create geographically-aware PFQuerys and also how to handle merging new and past data. Those with a stronger computer science background might notice that our algorithm has a quadratic complexity in all cases. Since we are dealing small datasets and super fast devices, this has little effect on Anywall performance.

4.2. Tracking User Location with CoreLocation

We now move our attention to CoreLocation. This Apple-made framework provides you with all the APIs you need to interact with an iOS device's GPS capabilities. In this tutorial, we are going to concentrate on CoreLocation's CLLocationManager, the object responsible for monitoring a device's location. By implementating the CLLocationManagerDelegate protocol in our view controller, we'll gain access to an assortement of useful methods. We will only be able to cover a small subset of the functionality available in this framework. For more information, check out Apple's Location Awareness Programming Guide and the CoreLocation Documentation. If you are working on your own project, note that you will need to import the CoreLocation framework.

The first step is to create and initialize our CLLocationManager object in our PAWWallViewController's viewDidLoad method. The following code creates and configures our location manager object.

// PAWWallViewController.h

@property (nonatomic, strong) CLLocationManager *locationManager;

// PAWWallViewController.m

- (void)viewDidLoad 
{
   ...
   locationManager = [[CLLocationManager alloc] init];

   locationManager.delegate = self;
   locationManager.desiredAccuracy = kCLLocationAccuracyBest;

   // Set a movement threshold for new events
   locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;

   [locationManager startUpdatingLocation];

   // Set initial location if available
   CLLocation *currentLocation = locationManager.location;
   if (currentLocation) {
      PAWAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
      appDelegate.currentLocation = currentLocation;
   }
}

There are a variety of properties we can use to fine tune the behaviour of the GPS device to suit our applicaiton's needs. In our case, we want to use the best accuracy possible, but only be notified of a location change when the user moves more than ten meters. This helps reduce the number of unneccessary server calls. As part of the configuration, we also set the PAWWallViewController as the delegate for the location manager object. We will see how the delegation methods are used in the next code block.

After creating the locationManager, it does not automatically start reporting the user's location. We need to start the monitoring process manually by calling the startUpdatingLocation method. We can also stop the locationManager by calling the stopUpdatingLocation method.

The last piece of code in this method tries to initialize the cached location if the locationManager already knows the user's position. Notice that we store this information in the currentLocation property in the app delegate. As we mentionned in Section 3.1, we use the app delegate to cache the last known position of the user using a property called currentLocation. Using an custom setter for this property, the app delegate also posts a notification to the NSNotificationCenter to inform other classes that the user has moved. We discuss the NSNotificationCenter in more details in Section 1.2.

It is likely that no location is yet available. This is perfectly acceptable, since the locationManager will start informing its delegate of the device's location as soon as it is known. We obtain these messages by implementing the delegate method locationManager:didUpdateToLocation:fromLocation:.

// PAWWallViewController.m

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{
   // Set new location and post a notification to the NSNotificationCenter
   PAWAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
   appDelegate.currentLocation = newLocation;
}

Notice that we run the same code as we did when trying to set the initial location in the viewDidLoad method. This means that everytime the user's location changes, we update our cache and post a notfication to the NSNotificationCenter. But what happens when the classes registered for this notification receive it?

We briefly covered this topic in Section 1.2. There are two view controllers in Anywall that subscribe to this notification: the PAWWallPostTableViewController which we'll cover in Section 5 and the PAWWallViewController. Since we are currently talking about the latter, let's take a look at what happens after the location is updated and the notification is sent.

In the viewDidLoad method of the PAWWallViewController we register this notification as follows.

// PAWWallViewController.m, viewDidLoad method

// Register for the user location change notification: kPAWLocationChangeNotification
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(locationDidChange:) 
                                             name:kPAWLocationChangeNotification   
                                           object:nil];

Using this line of code, we inform the NSNotificationCenter that we want the locationDidChange: method to be called everytime this notification is received. We can then use this method to update the pins displayed on the screen by calling the queryForAllPostsNearLocation: withNearbyDistance: we discussed in the first part of this section. Take a look at implemention of the locationDidChange: method.

// PAWWallViewController.m

- (void)locationDidChange:(NSNotification *)note;
{
   PAWAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

   // If we haven't drawn the search radius on the map, initialize it.
   if (self.searchRadius == nil) 
   {
      self.searchRadius = 
       [[PAWSearchRadius alloc] initWithCoordinate:appDelegate.currentLocation.coordinate 
                                            radius:appDelegate.filterDistance];
      [mapView addOverlay:self.searchRadius];
   } 
   else
   {
      self.searchRadius.coordinate = appDelegate.currentLocation.coordinate;
   }

   // Update the map with new pins:
   [self queryForAllPostsNearLocation:appDelegate.currentLocation 
                   withNearbyDistance:appDelegate.filterDistance];
   // And update the existing pins to reflect any changes in filter distance:
   [self updatePostsForLocation:appDelegate.currentLocation 
             withNearbyDistance:appDelegate.filterDistance];
}

The first if statement is used to draw or move the search radius on the map. We won't cover much about the search radius in this tutorial, so check out Location Awareness Programming Guide and the full Anywall code if you are curious about how it works

The last lines of this method are the most important. We first call the queryForAllPostsNearLocation: method. If you recall, we covered this method in the in Section 4.1. It is charge of making the Parse query for all wall post obejcts near a given location and merging the results with the ones currently being displayed. So we call this method with the user's updated location and thus update the displayed wall posts to reflect this new location. We finish by calling the updatePostsForLocation: method. This is a simple method that performs a task very similar to the third step of the queryForAllPostsNearLocation: that we discussed in Section 4.1. We loop through all wall posts and hide their message if they are now outside the search radius and show their message if they are now inside the radius.

This completes our query-location cycle. First the locationManager updates the cached location property. This causes a notification to be broadcasted to the PAWWallViewController and PAWWallPostTableViewController. The PAWWallViewController uses the new location to make a query to Parse and then updates its displayed pins on the map. But how do we display these posts on a map? That is our next topic of discussion.

4.3. MapKit

MapKit is a truly amazing framework that makes embedding and annotating maps dead simple. We will only be able to scratch the surface of MapKit, so if you need more information you can check out Apple's MapKit Documentation or the Location Awareness Programming Guide. This section of the tutorial will focus on the MKMapView class and the MKAnnotation protocol. We'll start by looking at how to embed a map as a view using the MKMapView class. Then we'll see how the MKMapView's delegate protocol can be used to draw annotations on the map using a custom implementation of theMKAnnotation protocol. Finally, we'll finish our overview of MapKit with a brief look at the other delegation methods of MKMapView used in Anywall. If you are working on your own project, note that you will need to import the MapKit framework.

Embeding a map view is very similar to adding any other controls in your view controller. In the a nib, we drag and and drop the Map View control in our view and link it to and IBOutlet of type MKMapView in our view controller. In Anywall, we added a map view in the PAWWallViewController.xib located in the "Project > XIB Ressources" folder.


Adding an MKMapView to a view controller

@interface PAWWallViewController : UIViewController <MKMapViewDelegate,...>

@property (nonatomic, strong) IBOutlet MKMapView *mapView;

Notice that in the xib file, we also set the PAWWallViewController as the delegate for the map view. The MKMapView shares many similarities with the famed UITableView class when it comes to delegation. If you are familiar with the UITableView class, you will definitely have come across the tableView:cellForRowAtIndexPath: method. In this method, you will typically use the indexPath parameter to create and return a UITableViewCell object, used to represent a row in the table. An analogous delegation method exists in the MKMapViewDelegate, called mapView:viewForAnnotation:. In a similar way, we will use the annotation parameter (of type MKAnnotation) to create an MKAnnotationView object used to represent a pin on the map.

The key difference is that MKAnnotation is not a class, it is a protocol. We cannot simply create new objects of type MKAnnotation, we need to create our own class that will implement the MKAnnotation protocol. Thankfully, it is not complicated to create such a class. In Section 4.1, we briefly touched on this. Recall the following code:

// We create a new 'MKAnnotation'
PAWPost *newPost = [[PAWPost alloc] initWithPFObject:object];
...
// We add all new 'MKAnnotation' objects to the map
[mapView addAnnotations:newPosts];

When we obtained the new query results, we buit an array of these PAWPost objects and added them to the map. As you may have guessed, PAWPost implements the MKAnnotation protocol.

Before we take a look at this class, let's take a moment to think about the flow of information we've seen up until now. First, the CLLocationManager notices a change in the GPS location and informs the view controllers through the use of the NSNotificationCenter. When the PAWWallViewController hears this news, it initiates a query to Parse asking for all wall posts around this new location. When the server replies, the new data is merged with the old and a set of 'MKAnnotation's are added and removed from the map view. To update its display, the map view calls its delegate's mapView:viewForAnnotation: method for each MKAnnotation it has. As we'll see in more details shortly, the delegate (i.e PAWWallViewController) uses this method to create a view for this annotation using an MKAnnotationView object. Finally, the map view displays these annotation views as pins on the map. Try to keep all of this in mind as we dig deeper and deeper. So now that we know where we are going, let's take a quick glance at the PAWPost class.

The MKAnnotation protocol is not a demanding one. It contains three readonly properties: a title and a subtitle of type NSString, and a location of type CLLocationCoordinate2D. The readonly attribute of these properties is often the source of confusion. To be able to modify these properties in your own implementation, you will need to re-declare them without the read-only attribute. You can do this in the header file, or in an anonymous category. The benefit of the latter is that outside classes will still perceive the properties as read-only, and will not be able to modify them. An anonymous category is simply an interface declaration made inside of the implementation file. Let's take a look at this.

// PAWPost.h
@interface PAWPost : NSObject <MKAnnotation>
...
//PAWPost.m
@interface PAWPost ()

@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;

@property (nonatomic, strong) PFObject *object;
@property (nonatomic, strong) PFGeoPoint *geopoint;
@property (nonatomic, strong) PFUser *user;

@end

@implementation PAWPost
...

Notice that we also added three other properties that we will use to facilitate our integration with Parse. The implementation of this file is nothing out of the ordinary. First, we have two intializers. The first is our standard designated initializer where we set all three required properties. The second uses a PFObject to initialize the required properties. This is the initializer we used when we created PAWPost objects after the Parse query was made in Section 4.1.

- (id)initWithCoordinate:(CLLocationCoordinate2D)aCoordinate 
               andTitle:(NSString *)aTitle
             andSubtitle:(NSString *)aSubtitle;
{
   self = [super init];
   if (self) {
      // Initialize the title, subtitle and coordinate
      self.coordinate = aCoordinate;
      self.title = aTitle;
      self.subtitle = aSubtitle;
      self.animatesDrop = NO;
   }
   return self;
}

- (id)initWithPFObject:(PFObject *)anObject;
{
   // Extract the PFUser and PFGeoPoint
   self.object = anObject;
   self.geopoint = [anObject objectForKey:kPAWParseLocationKey];
   self.user = [anObject objectForKey:kPAWParseUserKey];
   
   // Extract the title, subtitle and coordinate
   CLLocationCoordinate2D aCoordinate = 
      CLLocationCoordinate2DMake(self.geopoint.latitude, self.geopoint.longitude);
   NSString *aTitle = [anObject objectForKey:kPAWParseTextKey];
   NSString *aSubtitle = 
      [[anObject objectForKey:kPAWParseUserKey] objectForKey:kPAWParseUsernameKey];

   // Set the title, subtitle and coordinate using the designated initializer
   return [self initWithCoordinate:aCoordinate 
                          andTitle:aTitle 
                       andSubtitle:aSubtitle];
}

There are two other methods in this class. The first is equalTo: which compares two PAWPost objects and determines if they are equal. We used this method in Section 4.1 when verifying if a post obtained in the query already existed on the map (see steps 1 and 2 of the algorithm). The last method in PAWPost is setTitleAndSubtitleOutsideDistance:. We also used this one in Section 4.1, but in step 3 of the algorithm. This method sets the title of the annotation to generic "You are too far" message when specified that it is now located outside the bounds of our search radius. You can take a look at the code for these two methods by downloading the source for Anywall.

With an implementation of MKAnnotation in hand, we can now start adding pins on the map using the mapView:viewForAnnotation delegate method. Remember that this method is called before each of the map view's PAWPost is dispalyed.

// PAWWallViewController.m

- (MKAnnotationView *)mapView:(MKMapView *)aMapView 
            viewForAnnotation:(id<MKAnnotation>)annotation
{
   // We will let the system handle the user location annotation (the blue dot)
   if ([annotation isKindOfClass:[MKUserLocation class]]) {
      return nil;
   }

   static NSString *pinId = @"CustomPinAnnotation";

   // Handle any other annotations
   if ([annotation isKindOfClass:[PAWPost class]])
   {
      // Try to dequeue an existing pin view first
      MKPinAnnotationView *pinView = 
         (MKPinAnnotationView*)[aMapView dequeueReusableAnnotationViewWithIdentifier:pinId];

      // If an existing pin view was not available, create one
      if (!pinView)
      {
         // Create a new annotation view (Note MKPinAnnotationView is a 
         // subclass of MKAnnotationView)
         pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
                                                   reuseIdentifier:pinIdentifier];
      }
      else // If one did exist, then set the parameter MKAnnotation to the new pinView
      {
         pinView.annotation = annotation;
      }
      
      // Set the characteristics of the annotation view
      pinView.pinColor = MKPinAnnotationColorRed;
      pinView.animatesDrop = [((PAWPost *)annotation) animatesDrop];
      pinView.canShowCallout = YES;

      return pinView;
   }
   // Return nil for any other (undefined) MKAnnotation
   return nil;
}

The implementation of this method is very similar to the analogous tableView:viewForRowAtIndexPath:. We begin by either creating a new annotation view of type MKPinAnnotationView or by reusing an existing one. By passing ourPAWPost object to the annotation view we are able to set the title, subtitle and coordinate information of our new view. We also set a few more properties of the annotation view to customize the display. The canShowCallout is a particularly important one. This property dictates whether taping on a pin will produce a callout box displaying the title and subtitle. When the annotation view is properly configured we return it.

We will finish this section by taking a brief look at other MKMapView delegate methods implemented by Anywall. The first two are the mapView:didSelectAnnotationView and mapView:didDeselectAnnotationView. As the names imply, these methods are called when a pin is selected or deselected on an MKMapView. In Anywall, we use these methods to highlight and unhighlight the associated cell in the UITableView displayed under the map. Another method of note is mapView:viewForOverlay. This method is used to take advantage of a more advanced feature of MapKit, MKOverlays. These are custom views that can be drawn on top of the map. In Anywall, this is used to draw the search radius that appears around the user. For the code associated to these methods, check out Anywall's source code.

4.4. Wrapping Up

We covered some of the most important topics of Anywall in this section. We began by dipping our toes in MapKit and CoreLocation by first looking at the all-important Parse query and the associated data management. Learning how geographic data is brought into the app was the first piece of the puzzle. We then dove head first in CoreLocation and MapKit and got not only an overview of these two frameworks, but also a clear picture of how everything comes together in Anywall. From the PFQuery, to the CLLocationManager posting notifications, to the MKMapView displaying PAWPost annotations, we've come full circle.

With all this in mind, we'll move our discussion toward the PAWWallPostTableViewController which mimics the data displayed in the MKMapView in a UITableView. But don't worry, by making use of the Parse's PFQueryTableViewController, this view controller is surprinsigly simple.

5. PFQueryTableViewController and PFGeoPoint

In this final section of the Anywall tutorial, we're going to look at the PAWWallPostTableViewController. This sublcass of PFQueryTableViewController mimics the data displayed in the PAWWallViewController's map view. To accomplish this without duplicating the same code, we make use of the NSNotificationCenter. As we discussed in

5.1. Container View Controllers

In iOS 5, Apple introduced the concept of Container View Controllers. This allows us to add a view controller as a child of another. An example of a container view controller is the UINavigationController where each view controller on the navigation stack is a child of the navigation controller. In Anywall we make use of this new ability to have a map view and a table view displayed on the same screen but managed by two different view controllers. We do this by adding the PAWWallPostTableViewController as a child of the PAWWallViewController. Take a look at how this works.

// PAWWallViewController

- (void)viewDidLoad 
{
   ...
   // Create the table view controller
   self.wallPostsTableViewController = 
      [[PAWWallPostsTableViewController alloc] initWithStyle:UITableViewStylePlain];
   self.wallPostsTableViewController.view.frame = CGRectMake(0.f, 208.f, 320.f, 208.f);

   // Add the PAWWallPostsTableViewController as a child of PAWWallViewController
   [self addChildViewController:self.wallPostsTableViewController];
   // Add the view of PAWWallPostsTableViewController as a 
   // subview of PAWWallViewController's view
   [self.view addSubview:self.wallPostsTableViewController.view];
   ...
}

We set this in the parent view controller, PAWWallViewController. UIKit will take care of forwarding all the typical view controller API methods like viewDidLoad and viewDidAppear, and we program both view controllers as if they were completely independant of eachother.

5.2. Configuring the Query Table

As a subclass of PFQueryTableViewController, creating this class will be a little different from the typicalUITableViewController` subclass. We'll begin by setting a few properties in order to tune the behaviour to our needs. Take a look.

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Customize the table
        // The className to query on
        self.className = kPAWParsePostsClassKey;

        // The key of the PFObject to display in the label of the default cell style
        self.keyToDisplay = kPAWParseTextKey;

        // Whether the built-in pull-to-refresh is enabled
        self.pullToRefreshEnabled = YES;

        // Whether the built-in pagination is enabled
        self.paginationEnabled = YES;

        // The number of objects to show per page
        self.objectsPerPage = kPAWWallPostsSearch;
    }
    return self;
}

The most important properties set here are className and keyToDisplay. These allow us to specify what class of PFObject this table will be displaying as well as the key that will be used for the cell's title. In our case, we'll want to display wall post objects and their associated messages as the title. Notice that we use constants as the value for many of these keys. This helps us avoid common mistakes associates with incorrect strings.

Next, we need to define the query this table will use to obtain data from Parse. We will use a query similar to the one we defined for the map view in Section 4.1. Since we are using a subclass of PFQueryTableViewController, we don't need to worry about running this query ourselves. Instead, we override the queryForTable method and return the query we want the table view to use to populate its cells.

// PAWWallPostTableViewController.m

- (PFQuery *)queryForTable {
   PFQuery *query = [PFQuery queryWithClassName:self.className];

   // If no objects are loaded in memory, we look to the cache first to fill the table
   // and then subsequently do a query against the network.
   if ([self.objects count] == 0) {
      query.cachePolicy = kPFCachePolicyCacheThenNetwork;
   }

   // Query for posts near our current location.

   // Get our current location:
   PAWAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
   CLLocation *currentLocation = appDelegate.currentLocation;
   CLLocationAccuracy filterDistance = appDelegate.filterDistance;

   // And set the query to look by location
   PFGeoPoint *point = [PFGeoPoint geoPointWithLatitude:currentLocation.coordinate.latitude 
                                              longitude:currentLocation.coordinate.longitude];
   [query whereKey:kPAWParseLocationKey nearGeoPoint:point 
                                    withinKilometers:filterDistance / kPAWMetersInAKilometer];
   [query includeKey:kPAWParseUserKey];

   return query;
}

Notice that we use the same pattern we used in Section 4.1 to create the PFQuery. We begin by setting the cache policy in hopes that we can preload the table with existing data. Afterwards we use both the user's current location and the filter distance to create a geographically aware PFQuery. We also use the includesKey method to ask the query to include the PFUser objects associated with each wall post returned. This will allow us to display the name of the user associated with the wall post.

Unlike the PAWWallViewController, we don't need to worry about merging the data or even handling the response from the Parse server. The beauty of subclassing PFQueryTableViewController is that all this functionality comes built-in!

5.3. Loading and Displaying the Data

Our next step is to configure and display the data in the cells. Instead of the typical tableView:cellForRowAtIndexPath:, the PFQueryTableViewController provides us with a convinient alternative: tableView:cellForRowAtIndexPath:object:. After running the query, the controller will automatically associate each cell with one of the PFObjects returned. The object parameter provides us with this PFObject directly and saves us from having to maintain a seperate data structure to track the associations between our PFObjects and the table's index paths. The PFObjects are automatically obtained, sorted and stored according to the query we specified. Let's take a look at the implementation for this method.

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath 
                        object:(PFObject *)object 
{
   // Usual cell reuse dance
   static NSString *CellIdentifier = @"Cell";
   
   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
   if (cell == nil) {
      cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                    reuseIdentifier:CellIdentifier];
   }

   // Configure the cell
   
   cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
   cell.textLabel.numberOfLines = 0;
   cell.textLabel.font = [cell.textLabel.font fontWithSize:kPAWWallPostTableViewFontSize];
   // Here we set the title to the wall post message using the PFObject we got
   cell.textLabel.text = [object objectForKey:kPAWParseTextKey];
   // Here we set the detail text to the owner's name using the PFObject
   cell.detailTextLabel.text = 
       [[object objectForKey:kPAWParseUserKey] objectForKey:kPAWParseUsernameKey]; 
   return cell;
}

We start with the usual reuse identifier dance. Once we have our cell object, we set the title and details labels using the given PFObject.

Now that our controller is configured, the associated query is set and the table cells are properly built, we need to specify when the data should be reloaded. Just like our PAWWallViewController in Section 4.2 we need to reload the wall posts when the user changes location, when the user adds a new wall posts, or when search radius is modified. As we saw in Section 1.2, Section 3.x and Section 4.2 we send notifications using the NSNotificationCenter when these events happen. Just like the PAWWallViewController, we register for these notifications and use them as cues that the data should be reloaded. However, unlike the PAWWallViewController, reloading the data in a subclass of PFQueryTableViewController is very simple. We can simply use the loadObjects method to manually re-execute the query and update the displayed items.

// PAWWallPostTableViewController.m

- (void)viewDidLoad
{
   ...
   // Add view controller as an observer of the notifications
   [[NSNotificationCenter defaultCenter] addObserver:self 
                                           selector:@selector(distanceFilterDidChange:) 
                                              name:kPAWFilterDistanceChangeNotification object:nil];    
   [[NSNotificationCenter defaultCenter] addObserver:self 
                                           selector:@selector(locationDidChange:) 
                                                name:kPAWLocationChangeNotification object:nil];
   [[NSNotificationCenter defaultCenter] addObserver:self 
                                           selector:@selector(postWasCreated:)  
                                               name:kPAWPostCreatedNotification object:nil];
}

...

// Re-execute the query and update the display

- (void)distanceFilterDidChange:(NSNotification *)note {
    [self loadObjects];
}

- (void)locationDidChange:(NSNotification *)note {
    [self loadObjects];
}

- (void)postWasCreated:(NSNotification *)note {
    [self loadObjects];
}

We now have a flow very similar to that of the PAWWallViewController. First, when a notification is posted, the loadObjects method is called. The PAWWallPostTableViewController then uses the queryForTable method to obtain a PFQuery. After running this query, the view controller takes care of handling the incoming data and merging it with the old. When it is time to display the cells on screen, the tableView:cellForRowAtIndexPath:object method is called using each PFObject returned by the query. We configure the cells in this method and they are then displayed on screen.

5.3 Wrapping Up

In this section of the tutorial, we got a quick introduction to the PFQueryTableViewController and saw how it is used as a contained view controller in Anywall. We were able to greatly reduce the complexity of our view controller by using this Parse class since many of the boiler plate algorithms are already built-in. We also saw that by using the NSNotificaitonCenter we were able to reduce code duplication and make use of events generated by other view controllers to reload the table's data. Despite the many differences, the overall flow of information was very similar to the one of the PAWWallViewController.

If you want to find out more about the `PFQueryTableViewController, check out the Parse Query Table tutorial.