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

Sign Up

Anywallicon
Anywall

Explore the use of PFGeoPoints, PFUser and PFConfig 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
PFConfig
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 six 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. See how you can add Facebook authentication as an option. 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 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 PAWWallPostsTableViewController makes use of the PFQueryTableViewController to mimic the posts displayed in the map view.

6. Remote Configuration
Learn how to remotely manage app settings using PFConfig. This section will show you how to do this by configuring two settings used in Anywall: the search radius options and the maximum character count for newly created wall posts.

1. Anywall Overview

1.1. Architecture

Anywall consists of six view controllers. The PAWLoginViewController is the initial screen that is presented and it allows a user to either log in or register through the PAWNewUserViewController. 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 and 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.

Most of these view controllers are supported by a xib file that describes the user interface (UI). The PAWWallPostsTableViewController builds its UI using PFQueryTableViewController.

The PAWConstants header contains the constants used in the the project.

Finally, the PAWConfigManager acts as a proxy to PFConfig to manage app parameters that are configured remotely.

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 in turn perform whatever task is needed. The following code snippets outline an example flow of how this is used in Anywall.

First, we declare a constant string that will be used to represent this notification:

// PAWConstants.h

static NSString * const PAWCurrentLocationDidChangeNotification = 
    @"PAWCurrentLocationDidChangeNotification";

Then, when a new location is set we post a notification:

// PAWWallViewController.m

- (void)setCurrentLocation:(CLLocation *)currentLocation {
    if (self.currentLocation == currentLocation) {
        return;
    }

    _currentLocation = currentLocation;

    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:PAWCurrentLocationDidChangeNotification
                                                            object:nil
                                                          userInfo:@{ kPAWLocationKey : currentLocation } ];
    });
    // ...
}

Now, we need to simply set the currentLocation property when we learn that the user's location has changed. We detect location changes through one of the CLLocationManager's delegate method. 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 
    self.currentLocation = newLocation;
}

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

// PAWWallPostsTableViewController.m

- (instancetype)initWithStyle:(UITableViewStyle)style {
    self = [super initWithStyle:style];
    if (self) {
        ...
        // The view controller requests to be an observer of
        // the notification PAWCurrentLocationDidChangeNotification
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(locationDidChange:)
                                                     name:PAWCurrentLocationDidChangeNotification
                                                   object:nil];
    }
    return self;
}

// When the PAWCurrentLocationDidChangeNotification 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 method:

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:PAWCurrentLocationDidChangeNotification
                                                  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 PAWCurrentLocationDidChangeNotification, sent when a user changes location. The other two are the PAWFilterDistanceDidChangeNotification, sent when the search radius distance is changed, and the PAWPostCreatedNotification, sent when the user creates a new post. All three of these notifications are observed by the PAWWallPostsTableViewController. The PAWWallViewController does not need the PAWCurrentLocationDidChangeNotification and we'll see why when we discuss this in more detail. As mentioned above, the PAWWallViewController displays nearby wall posts in a map view, and the PAWWallPostsTableViewController displays the 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 can use them to update their data and display.

1.3. Wrapping Up

In this first section of the tutorial we got a broad overview of the application's classes 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. You may want to give users the ability to register with their own username and password or use Facebook for authentication. 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 take a look at the code needed to login and create new users using the PFUser object. We'll discuss how to implement Facebook Login to return a PFUser object. 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. The PAWLoginViewController provides an option for users to log in or sign up through Facebook. We will be discussing the implementation of these two view controllers simultaneously since their implementations are very similar.

2.1. Validating the Data

The PAWLoginViewController user interface contains UITextFields to capture the username and password information. A "Login" button allows the user to submit the entered information. Field validation is performed on the text fields when the button is pressed:

- (void)processFieldEntries {
    // Get the username text, store it in the app delegate for now
    NSString *username = self.usernameField.text;
    NSString *password = self.passwordField.text;
    NSString *noUsernameText = @"username";
    NSString *noPasswordText = @"password";
    NSString *errorText = @"No ";
    NSString *errorTextJoin = @" or ";
    NSString *errorTextEnding = @" entered";
    BOOL textError = NO;

    // Messaging nil will return 0, so these checks implicitly check for nil text.
    if (username.length == 0 || password.length == 0) {
        textError = YES;

        // Set up the keyboard for the first field missing input:
        if (password.length == 0) {
            [self.passwordField becomeFirstResponder];
        }
        if (username.length == 0) {
            [self.usernameField becomeFirstResponder];
        }
    }

    if ([username length] == 0) {
        textError = YES;
        errorText = [errorText stringByAppendingString:noUsernameText];
    }

    if ([password length] == 0) {
        textError = YES;
        if ([username length] == 0) {
            errorText = [errorText stringByAppendingString:errorTextJoin];
        }
        errorText = [errorText stringByAppendingString:noPasswordText];
    }

    if (textError) {
        // Uh oh, show the user what's wrong
        return;
    }
    ...
}

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 an additional check:

- (void)processFieldEntries {
    ...
    NSString *passwordMismatchText = @"enter the same password twice";
    ...
    if (username.length == 0 || password.length == 0 || passwordAgain.length == 0) {
        ...
        if (password.length == 0 || passwordAgain.length == 0) {
            if (username.length == 0) {
                errorText = [errorText stringByAppendingString:joinText];
            }
            errorText = [errorText stringByAppendingString:passwordBlankText];
        }
    } else if ([password compare:passwordAgain] != NSOrderedSame) {
        // We have non-zero strings.
        // Check for equal password strings.
        textError = YES;
        errorText = [errorText stringByAppendingString:passwordMismatchText];
        [self.passwordField becomeFirstResponder];
    }
    
    if (textError) {
        // Uh oh, show the user what's wrong
        return;
    }
    ...
}

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.2. 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:

- (void)processFieldEntries {
   ...
    PFUser *user = [PFUser user];
    user.username = username;
    user.password = password;
    [user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (error) {
            // Display an alert view to show the error message
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[error userInfo][@"error"]
                                                                message:nil
                                                               delegate:self
                                                      cancelButtonTitle:nil
                                                      otherButtonTitles:@"OK", nil];
            [alertView show];
            // Bring the keyboard back up, because they probably need to change something.
            [self.usernameField becomeFirstResponder];
            return;
        }

        // Handle the success path
        ...
    }];
}

The block parameter of this function is called when the application receives a reply from Parse. 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. Since it's likely that the user will need to modify some of the entered information after an error, we 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 the signup is successful, we want to display the main view controller, PAWWallViewController. The steps taken to display the main view controller utilizes delegation heavily so let's walk through the flow. In the PAWNewUserViewController we define a protocol called PAWNewUserViewControllerDelegate. Adopters of this protocol must implement the newUserViewControllerDidSignup: method that's called upon successful sign up:

// PAWNewUserViewController.h

@protocol PAWNewUserViewControllerDelegate <NSObject>
- (void)newUserViewControllerDidSignup:(PAWNewUserViewController *)controller;
@end

We call this delegate method after user signs up and the view controller is dismissed:

// PAWNewUserViewController.m

- (void)processFieldEntries {
   ...
    PFUser *user = [PFUser user];
    user.username = username;
    user.password = password;
    [user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
        if (error) {
            // Handle the error path
            ...
            return;
        }
        
        // We dismiss the view controller
        [self dismissViewControllerAnimated:YES completion:nil];
        
        // We call the delegate method that's notified when a sign up is successful
        [self.delegate newUserViewControllerDidSignup:self];
    }];
}

When the PAWLoginViewController instantiates and presents the PAWNewUserViewController, it sets itself up as the delegate so it can be notified when a user signs up:

// PAWLoginViewController.m

- (void)presentNewUserViewController {
    PAWNewUserViewController *viewController =
    [[PAWNewUserViewController alloc] initWithNibName:nil
                                               bundle:nil];
    viewController.delegate = self;
    [self.navigationController presentViewController:viewController
                                            animated:YES
                                          completion:nil];
}

We now implement the newUserViewControllerDidSignup: delegate method to handle the successful sign up. We'll fill out the details of what the handler does shortly:

// PAWLoginViewController.m

- (void)newUserViewControllerDidSignup:(PAWNewUserViewController *)controller {
   // Sign up successful
}

Recall, that when the user signs up or logs in successfully, we want to show the main view controller. At this point the PAWNewUserViewController has informed the PAWLoginViewController that a user has signed up. We can set up the PAWLoginViewController to "forward" this information by having it define a protocol with a method that we can call. We can use the same method in cases where the user has successfully logged in.

So let's have PAWLoginViewController define a protocol that delegates can adopt to be notified of a successful login or sign up:

@protocol PAWLoginViewControllerDelegate <NSObject>
- (void)loginViewControllerDidLogin:(PAWLoginViewController *)controller;
@end

We can now call this delegate method from newUserViewControllerDidSignup: delegate method implementation when a new user signs up:

- (void)newUserViewControllerDidSignup:(PAWNewUserViewController *)controller {
    // Sign up successful
    [self.delegate loginViewControllerDidLogin:self];
}

The last piece of the puzzle is presenting the main view controller. This logic resides in the app delegate. When the app delegate sets up the PAWLoginViewController it can set itself up as the delegate for that view controller:

- (void)presentLoginViewController {
    // Go to the welcome screen and have them log in or create an account.
    PAWLoginViewController *viewController =
    [[PAWLoginViewController alloc] initWithNibName:nil
                                             bundle:nil];
    viewController.delegate = self;
    [self.navigationController setViewControllers:@[ viewController ]
                                         animated:NO];
}

The app delegate can implement the required delegate method to display the main view controller:

- (void)loginViewControllerDidLogin:(PAWLoginViewController *)controller {
    // Private method to display the main view controller
    [self presentWallViewControllerAnimated:YES];
}

The process for logging in a user is very similar to the signup flow. We start by calling the PFUser's logInWithUsernameInBackground: password: block: method with the username and password values, then handle 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:

- (void)processFieldEntries {
   ...
    [PFUser logInWithUsernameInBackground:username
                                 password:password
                                    block:
     ^(PFUser *user, NSError *error) {
         // Tear down the activity view in all cases.
         self.activityViewVisible = NO;
         
         if (user) {  // Login successful
             [self.delegate loginViewControllerDidLogin:self];
         } else {  // Login failed
             NSString *alertTitle = nil;
             if (error) {
                 // Something else went wrong
                 alertTitle = [error userInfo][@"error"];
             } else {
                 // the username or password is probably wrong.
                 alertTitle = @"Couldn’t log in:\nThe username or password were wrong.";
             }
             UIAlertView *alertView =
             [[UIAlertView alloc] initWithTitle:alertTitle
                                        message:nil
                                       delegate:self
                              cancelButtonTitle:nil
                              otherButtonTitles:@"OK", nil];
             [alertView show];
             
             // Bring the keyboard back up, because they'll probably need to change something.
             [self.usernameField becomeFirstResponder];
         }
    }];
}

Unlike the registration view controller, we start with the successful case by checking if a user object is returned. If it exists, we call the PAWLoginViewController delegate's loginViewControllerDidLogin: method that eventually displays 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.3. Logging in with Facebook

Anywall users can also sign up or log in by tapping the "Log in with Facebook" button. This calls the logInWithPermissions:block: method in the PFFacebookUtils class to initiate the Facebook authentication flow:

- (IBAction)loginWithFacebookPressed:(id)sender {
    [PFFacebookUtils logInWithPermissions:nil block:^(PFUser *user, NSError *error) {
        if (!user) {
            // Handle error path
        } else {
            // Handle success path
        }
    }];
}

If the user object returned is nil, then an error occurred. The error handling path uses Facebook's FBErrorUtility class to categorize the error and display an appropriate message in a UIAlertView:

NSString *alertMessage, *alertTitle;
if (error) {
    FBErrorCategory errorCategory = [FBErrorUtility errorCategoryForError:error];
    if ([FBErrorUtility shouldNotifyUserForError:error]) {
        // If the SDK has a message for the user, surface it.
        alertTitle = @"Something Went Wrong";
        alertMessage = [FBErrorUtility userMessageForError:error];
    } else if (errorCategory == FBErrorCategoryAuthenticationReopenSession) {
        // It is important to handle session closures. We notify the user.
        alertTitle = @"Session Error";
        alertMessage = @"Your current session is no longer valid. Please log in again.";
    } else if (errorCategory == FBErrorCategoryUserCancelled) {
        // The user has cancelled a login. You can inspect the error
        // for more context. Here, we will simply ignore it.
        NSLog(@"user cancelled login");
    } else {
        // Handle all other errors in a generic fashion
        alertTitle  = @"Unknown Error";
        alertMessage = @"Error. Please try again later.";
    }
    
    if (alertMessage) {
        [[[UIAlertView alloc] initWithTitle:alertTitle
                                    message:alertMessage
                                   delegate:nil
                          cancelButtonTitle:@"Dismiss"
                          otherButtonTitles:nil] show];
    }
}

If a user object is returned in the block handler, then the login completed successfully. The success path makes a Facebook API request to get the user's information before calling the loginViewControllerDidLogin: delegate method to trigger the PAWWallViewController display:

[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
    dispatch_block_t completion = ^{
        // Show the logged in view
        [self.delegate loginViewControllerDidLogin:self];
    };
    
    if (error) {
        completion();
    } else {
        // Save the name on Parse
        [PFUser currentUser][@"name"] = user.name;
        [[PFUser currentUser] saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
            completion();
        }];
    }
}];

The Facebook SDK's startForMeWithCompletionHandler: method in the FBRequestConnection class initiates the request for user information. The completion handler of this method sets up another block that calls loginViewControllerDidLogin: to show the logged in view. If the Facebook API request is successful, it returns an FBGraphUser that's used to populate the PFUser name field. Whether the API request fails or succeeds, the logged in view will eventually be shown.

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
    if ([PFUser currentUser]) {
        // A user was cached, so skip straight to the main view
        [self presentWallViewControllerAnimated:NO];
    } else {
        // No cached user, go to the welcome screen and 
        // have them log in or create an account.
        [self presentLoginViewController];
    }   
   ...
}

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 call the presentWallViewControllerAnimated: private method that instantiates a PAWWallViewController sets it as the root of our navigation controller. If there is no cached user, then we follow the same procedure, but we call the private presentLoginViewController method that sets the PAWLoginViewController as the root of the navigation controller.

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 ensuring that the form data was plausible by validating it locally. Next, we looked at logging in and registering users using Parse. We then saw how to add Facebook integration. 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 mentioned 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 safely stored in Parse, we'll take a quick detour to see how the NSNotificationCenter 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 their 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 cover those in this section but take a look at 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. 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. 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, then extracting 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 the currently logged in PFUser
   PFUser *user = [PFUser currentUser];
   
   // Get user's current location
   // CLLocation *currentLocation = ...;  // TBD
   CLLocationCoordinate2D currentCoordinate = currentLocation.coordinate;
   ...
}

The current PFUser is obtained by using the static method currentUser. Getting user's location is a little more involved. We will make use of the fact that PAWWallViewController holds the user's current location is also responsible for presenting the PAWWallPostCreateViewController. We can set up PAWWallViewController as a data source to provide the current location we need. To do this, we will have PAWWallPostCreateViewController define a protocol that can be adopted to implement the required method to provide the location data:

// PAWWallPostCreateViewController.h

@protocol PAWWallPostCreateViewControllerDataSource <NSObject>
- (CLLocation *)currentLocationForWallPostCrateViewController:(PAWWallPostCreateViewController *)controller;
@end

We can then have the PAWWallViewController adopt this protocol and register as a data source before presenting the PAWWallPostCreateViewController:

// PAWWallViewController.m

@interface PAWWallViewController ()
<PAWWallPostsTableViewControllerDataSource, ...>
@end

@implementation PAWWallViewController
...
- (void)presentWallPostCreateViewController {
    PAWWallPostCreateViewController *viewController =
    [[PAWWallPostCreateViewController alloc] initWithNibName:nil
                                                      bundle:nil];
    viewController.dataSource = self;
    [self.navigationController presentViewController:viewController
                                            animated:YES
                                          completion:nil];
}

The data source's relevant method implementation simply returns the currentLocation property:

- (CLLocation *)currentLocationForWallPostCrateViewController:(PAWWallPostCreateViewController *)controller {
    return self.currentLocation;
}

Section 4.2 will investigate how the user location is obtained and stored. For now, just assume it is available and ready to use. We access it through the currentLocation property that is of type CLLocation. The class has a coordinate property that we can use to get the user's latitude and longitude. Now that we've set up our location data source we can fill in the dots in the postPost: method:

- (IBAction)postPost:(id)sender;
{
    ...
    // Get user's current location
    CLLocation *currentLocation = [self.dataSource currentLocationForWallPostCrateViewController:self];
    CLLocationCoordinate2D currentCoordinate = currentLocation.coordinate;
    ...
}

At this point, we can create our Parse objects. We'll start by creating our PFGeoPoint using the values from our CLLocationCoordinate2D, 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:PAWParsePostsClassName];
    postObject[PAWParsePostTextKey] = self.textView.text;
    postObject[PAWParsePostUserKey] = user;
    postObject[PAWParsePostLocationKey] = currentPoint;

    // Use PFACL to restrict future modifications to this object.
    PFACL *readOnlyACL = [PFACL ACL];
    [readOnlyACL setPublicReadAccess:YES];
    [readOnlyACL setPublicWriteAccess:NO];
    postObject.ACL = readOnlyACL;
    ...
}

Notice that we use string constants as the values for the keys in postObject (such as PAWParsePostsClassName). These constants are all defined in PAWConstants.h 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:

- (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][@"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:PAWPostCreatedNotification
                 object:nil];
            });
        } else {
            NSLog(@"Failed to save.");
        }
    }];

    // Dismiss this view controller and return to the main view
    [self dismissViewControllerAnimated:YES completion:nil];
}

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 that 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 NSNotificationCenter. 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 sent out after a new wall post is successfully saved. 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(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:PAWPostCreatedNotification
                                                        object:nil];
});
}

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

// PAWWallViewController.m and PAWWallPostsTableViewController.m

[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(postWasCreated:) 
                                             name:PAWPostCreatedNotification 
                                           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 required 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 bottoms 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 arbitrarily 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 *query = [PFQuery queryWithClassName:PAWParsePostsClassName];

    // 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) {
        query.cachePolicy = kPFCachePolicyCacheThenNetwork;
    }

    // Create a PFGeoPoint using the current location (to use in our query)
    PFGeoPoint *point = [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
    [query whereKey:PAWParsePostLocationKey
       nearGeoPoint:point
   withinKilometers:PAWWallPostMaximumSearchDistance];
    
    // Include the associated PFUser objects in the returned data
    [query includeKey:PAWParsePostUserKey];
    
    // Limit the number of wall posts returned to 20
    query.limit = PAWWallPostsSearchDefaultLimit;
    ...
}

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
    [query 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
        }
    }];
}

The completion block is where 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, discreetly remove posts that were not returned in the query, and leave all the others as-is. To help us understand this, take a look at the following diagram.

Diagram of the posts sets

The posts we've 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 displayed. 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:PAWWallPostsSearchDefaultLimit];

// In this array we'll store the newly received posts for use
// in step 2.
NSMutableArray *allNewPosts = [[NSMutableArray alloc] initWithCapacity:[objects count]];

// Loop through all returned PFObjects
for (PFObject *object in objects) {
    PAWPost *newPost = [[PAWPost alloc] initWithPFObject:object];
    [allNewPosts addObject:newPost];
    
    // Now we check if we already had this wall post
    if (![_allPosts containsObject:newPost]) {
        // 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 currently 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:PAWWallPostsSearchDefaultLimit];

// Loop through all the the wall posts we currently have
for (PAWPost *currentPost in _allPosts) {
    // Now we check if it isn't in the newly received posts
    if (![allNewPosts containsObject:currentPost]) {
        // 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
        [postsToRemove addObject:currentPost];
    }
}

Similar to the last check, we are comparing two arrays. The difference here is that we are comparing items from allNewPosts with those from the _allPosts array. 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 = self.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
// and we add all new posts to both the cache and the map

[self.mapView removeAnnotations:postsToRemove];
[self.mapView addAnnotations:newPosts];

[_allPosts addObjectsFromArray:newPosts];
[_allPosts removeObjectsInArray:postsToRemove];

// Set the initial load flag
self.mapPinsPlaced = YES;

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.

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 implementing the CLLocationManagerDelegate protocol in our view controller, we'll gain access to an assortment 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. We do this by defining a getter for the locationManager property that holds our CLLocationManager instance:

// PAWWallViewController.m

@interface PAWWallViewController ()
...
@property (nonatomic, strong) CLLocationManager *locationManager;
...
@end

@implementation PAWWallViewController
...
- (CLLocationManager *)locationManager {
    if (_locationManager == nil) {
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        _locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;
    }
    return _locationManager;
}

Whenever the locationManager is first accessed, it is initialized and its properties set up. There are a variety of properties we can use to fine tune the behavior of the GPS device to suit our app'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 unnecessary server calls. As part of the configuration, we set the PAWWallViewController as the delegate for the location manager object. We will see how the delegation methods are used in a few moments.

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 do this in the view controller's viewDidLoad method:

- (void)viewDidLoad {
    ...
    // Start location updates
    [self.locationManager startUpdatingLocation];
    
    // Cache any current location info
    CLLocation *currentLocation = self.locationManager.location;
    if (currentLocation) {
        self.currentLocation = currentLocation;
    }
}

Note that if the locationManager is nil when the startUpdatingLocation method is called, then it is initialized.

The last piece of code in this method tries to initialize the cached location if the locationManager already knows the user's position. We store this in the currentLocation property. If you recall, the custom setter for this property posts a notification to the NSNotificationCenter to inform other classes that the user has moved. This is discussed in Section 1.2.

We can also stop the locationManager by calling the stopUpdatingLocation method. We do this when the view has disappeared, by adding the call to the viewDidDisappear: method:

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    [self.locationManager stopUpdatingLocation];
}

This ensures that we're not asking for location updates when the view is not being shown. However, when the view does reappear we'll want to restart location updates. We do this by also calling startUpdatingLocation in the viewWillAppear: method:

- (void)viewWillAppear:(BOOL)animated {
    ...
    [self.locationManager startUpdatingLocation];
}

Once the locationManager is started, it will inform its delegate of the device's location as soon as it is known. We obtain these messages by implementing the locationManager:didUpdateToLocation:fromLocation: delegate method:

// PAWWallViewController.m

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation {
    self.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 every time the user's location changes, we update our cache and post a notification to the NSNotificationCenter. The PAWWallPostTableViewController subscribes to this notification and we'll cover how this is used in Section 5.

Since we know about the location change in PAWWallViewController we can react to it without going through the NSNotificationCenter. In the currentLocation setter, a location change triggers an update of the pins displayed by calling the queryForAllPostsNearLocation: withNearbyDistance: as discussed in the first part of this section. Here's the relevant code:

// PAWWallViewController.m

- (void)setCurrentLocation:(CLLocation *)currentLocation {
    ...
    // 1. Draw or move the search radius display
    ...
    // 2. Update the map with new pins:
    [self queryForAllPostsNearLocation:self.currentLocation
                    withNearbyDistance:filterDistance];
    // And update the existing pins to reflect any changes in filter distance:
    [self updatePostsForLocation:self.currentLocation
              withNearbyDistance:filterDistance];
}

We first draw the search radius to match our filter distance and move it as necessary. This code isn't covered in this tutorial, so check out the 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 objects 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 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 the MKAnnotation 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.

Embedding a map view is very similar to adding any other controls in your view controller. In the xib, we drag and and drop the Map View control into our view and link it to an IBOutlet of type MKMapView in our view controller. In Anywall, we've added a map view in the PAWWallViewController.xib.


Adding an MKMapView to a view controller

// PAWWallViewController.m

@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 that 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. Now, recall the following code:

// PAWWallViewController.m, in queryForAllPostsNearLocation:withNearbyDistance:

for (PFObject *object in objects) {
    // We create a new 'MKAnnotation'
    PAWPost *newPost = [[PAWPost alloc] initWithPFObject:object];
    ...
    if (![_allPosts containsObject:newPost]) {
        [newPosts addObject:newPost];
    }
}
...
// We add all new 'MKAnnotation' objects to the map
[self.mapView addAnnotations:newPosts];

When we obtained the new Parse query results, we built an array of 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 thus far. First, the CLLocationManager notices a change in the GPS location. The PAWWallViewController 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 MKAnnotations 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. 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. 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 read-only properties: a title and a subtitle of type NSString, and a location of type CLLocationCoordinate2D. The read-only attribute of these properties is often a 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) PFUser *user;

@property (nonatomic, assign) MKPinAnnotationColor pinColor;

@end

@implementation PAWPost
...

Notice that we also added two 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 initializers. 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:

- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate
                          andTitle:(NSString *)title
                       andSubtitle:(NSString *)subtitle {
    self = [super init];
    if (self) {
        self.coordinate = coordinate;
        self.title = title;
        self.subtitle = subtitle;
    }
    return self;
}

- (instancetype)initWithPFObject:(PFObject *)object {
    [object fetchIfNeeded];

    // Extract the PFGeoPoint
    PFGeoPoint *geoPoint = object[PAWParsePostLocationKey];
    
    // Extract the title, subtitle and coordinate
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(geoPoint.latitude,
                                                                   geoPoint.longitude);
    NSString *title = object[PAWParsePostTextKey];
    NSString *subtitle = object[PAWParsePostUserKey][PAWParsePostNameKey] ?:
    object[PAWParsePostUserKey][PAWParsePostUsernameKey];

    // Set the title, subtitle and coordinate using the designated initializer
    self = [self initWithCoordinate:coordinate andTitle:title andSubtitle:subtitle];
    if (self) {
        self.object = object;
        self.user = object[PAWParsePostUserKey];
    }
    return self;
}

There are two other methods in this class that are not shown. The isEqual: method compares two PAWPost objects and determines if they are equal. We will use this method in the PAWWallPostsTableViewController when deciding whether to highlight or unhighlight a table row as a pin is selected or deselected on the map. The setTitleAndSubtitleOutsideDistance: method is used in Section 4.1 to set the title of the annotation to a generic "You are too far" message when a visible pin 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 our 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 displayed:

// PAWWallViewController.m

- (MKAnnotationView *)mapView:(MKMapView *)mapVIew
            viewForAnnotation:(id<MKAnnotation>)annotation {
    // Let the system handle user location annotations.
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
        return nil;
    }

    static NSString *pinIdentifier = @"CustomPinAnnotation";

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

        if (!pinView) {
            // If an existing pin view was not available, create one.
            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 = [(PAWPost *)annotation pinColor];
        pinView.animatesDrop = [((PAWPost *)annotation) animatesDrop];
        pinView.canShowCallout = YES;

        return pinView;
    }

    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 reusing an existing one. By passing our PAWPost 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 briefly discussing the other MKMapView delegate methods implemented by Anywall. The first two are the mapView:didSelectAnnotationView and mapView:didDeselectAnnotationView methods. 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 row in the UITableView displayed under the map. Another method of note is mapView:rendererForOverlay. 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's current location. Check out Anywall's source code for the code associated with these delegate methods.

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 to 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 surprisingly simple.

5. PFQueryTableViewController and PFGeoPoint

In this 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 code, we make use of the NSNotificationCenter. As discussed in Section 1.2, these notifications allow classes to communicate with each other.

5.1. Container View Controllers

In Anywall, we make use of a container view controller to have our map view and 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.m

- (void)viewDidLoad {
    ...
    // Create the table view controller
    self.wallPostsTableViewController = [[PAWWallPostsTableViewController alloc] initWithStyle:UITableViewStylePlain];
    self.wallPostsTableViewController.dataSource = self;
    [self.view addSubview:self.wallPostsTableViewController.view];
    
    // Add the PAWWallPostsTableViewController as a child of PAWWallViewController
    [self addChildViewController:self.wallPostsTableViewController];
    [self.wallPostsTableViewController didMoveToParentViewController:self];
    ...
}

UIKit will take care of forwarding all the typical view controller API methods like viewDidLoad and viewDidAppear from the parent PAWWallViewController to the child PAWWallPostTableViewController. Therefore we can program both view controllers as if they were completely independent of each other.

5.2. Configuring the Query Table

As a subclass of PFQueryTableViewController, creating the PAWWallPostsTableViewController class will be a little different from the typical UITableViewController subclass. We'll begin by setting a few properties in order to tune the behavior to our needs:

- (instancetype)initWithStyle:(UITableViewStyle)style {
    self = [super initWithStyle:style];
    if (self) {
        // The Parse class to query
        self.parseClassName = PAWParsePostsClassName;

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

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

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

The most important properties set are className and textKey. 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.parseClassName];

    // 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:
    CLLocation *currentLocation =
    [self.dataSource currentLocationForWallPostsTableViewController:self];
    CLLocationAccuracy filterDistance = [[NSUserDefaults standardUserDefaults]
                                         doubleForKey:PAWUserDefaultsFilterDistanceKey];

    // And set the query to look by location
    PFGeoPoint *point =
    [PFGeoPoint geoPointWithLatitude:currentLocation.coordinate.latitude
                           longitude:currentLocation.coordinate.longitude];
    [query whereKey:PAWParsePostLocationKey
       nearGeoPoint:point
   withinKilometers:PAWMetersToKilometers(filterDistance)];
    [query includeKey:PAWParsePostUserKey];

    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 convenient alternative: tableView:cellForRowAtIndexPath:object:. After running the query, the view 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 separate data structure to track the associations between our PFObjects and the table view'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 {
    PAWPostTableViewCellStyle cellStyle = PAWPostTableViewCellStyleLeft;
    if ([object[PAWParsePostUserKey][PAWParsePostUsernameKey]
         isEqualToString:[[PFUser currentUser] username]]) {
        cellStyle = PAWPostTableViewCellStyleRight;
    }

    NSString *reuseIdentifier = nil;
    switch (cellStyle) {
        case PAWPostTableViewCellStyleLeft:
        {
            static NSString *leftCellIdentifier = @"left";
            reuseIdentifier = leftCellIdentifier;
        }
            break;
        case PAWPostTableViewCellStyleRight:
        {
            static NSString *rightCellIdentifier = @"right";
            reuseIdentifier = rightCellIdentifier;
        }
            break;
    }

    PAWPostTableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if (cell == nil) {
        cell =
        [[PAWPostTableViewCell alloc] initWithPostTableViewCellStyle:cellStyle
                                                     reuseIdentifier:reuseIdentifier];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    // Configure the cell
    PAWPost *post = [[PAWPost alloc] initWithPFObject:object];
    [cell updateFromPost:post];

    return cell;
}

The PAWPostTableViewCell used to provide the table view cell is a subclass of UITableViewCell. It has an updateFromPost: method that takes in a PFObject and sets up a customized display of the title and details. We won't discuss the UI details in the tutorial but you can take a look at the source code if you want to learn more.

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 previously, we send out 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:

- (instancetype)initWithStyle:(UITableViewStyle)style {
    self = [super initWithStyle:style];
    if (self) {
        ...

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(distanceFilterDidChange:)
                                                     name:PAWFilterDistanceDidChangeNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(locationDidChange:)
                                                     name:PAWCurrentLocationDidChangeNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(postWasCreated:)
                                                     name:PAWPostCreatedNotification
                                                   object:nil];
    }
    return self;
}

// 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 the updateFromPost: method of the PAWPostTableViewCell class and finally the wall posts are 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 UI class since many of the boiler plate algorithms are already built-in. We also saw that by using the NSNotificationCenter 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.

6. Remote Configuration

Anywall users can customize the search radius distance they want to use. It would be nice to be able to customize the available options even after the app has shipped. Anywall also sets a 140 maximum character limit for new wall posts. Being able to customize this setting without an app refresh is also appealing. Parse Config provides a simple way to store app parameters on Parse that can be used to configure the app without requiring an app update. We will use this feature to remotely configure the search radius and maximum character limit.

6.1. Setting Up The Configuration Parameters

The first step to using Parse Config is setting up the parameters we want to configure. These can be entered in the Parse Config Dashboard and they are simply key/value pairs. We will set up an availableFilterDistances parameter to hold an array of numbers representing the search radius options, and a postMaxCharacterCount parameter to hold a number representing the maximum character count for a new wall post.

Once we've set up these two parameters, we can access them from Anywall through the PFConfig class.

6.2. PFConfig Manager

We begin by setting up a class called PAWConfigManager to manage the app's configuration. This class acts as a singleton which means that there should only be one instance created over the lifetime of the app. It takes care of refreshing the PFConfig from Parse and providing access to the configuration parameters.

First, we'll set up a method to conditionally retrieve the latest PFConfig:

// PAWConfigManager.m

- (void)fetchConfigIfNeeded {
    static const NSTimeInterval configRefreshInterval = 60.0 * 60.0; // 1 hour

    if (self.config == nil ||
        self.configLastFetchedDate == nil ||
        [self.configLastFetchedDate timeIntervalSinceNow] * -1.0 > configRefreshInterval) {
        // Set the config to the cached version and start fetching new config
        self.config = [PFConfig currentConfig];

        // Set the date to current and use it as a flag that config fetch is in progress
        self.configLastFetchedDate = [NSDate date];

        [PFConfig getConfigInBackgroundWithBlock:^(PFConfig *config, NSError *error) {
            if (error == nil) {
                self.config = config;
                self.configLastFetchedDate = [NSDate date];
            } else {
                // Remove the flag to indicate that we should refetch the config once again
                self.configLastFetchedDate = nil;
            }
        }];
    }
}

If there is no saved configuration or more than an hour has passed since the last fetch, then PFConfig is refreshed when this method is called. The last-known PFConfig is automatically cached by the Parse framework and is available via the [PFConfig currentConfig] method call. The getConfigInBackgroundWithBlock: asynchronous request is used to get the latest PFConfig and during this fetch the configuration manager returns the cached version. If the fetch completes successfully, the configuration is updated with the latest data and the last fetch time property is updated.

An ideal place to call our configuration manager's fetchConfigIfNeeded method is when the app is launched:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...
    [[PAWConfigManager sharedManager] fetchConfigIfNeeded];
    return YES;
}

Successive app launches shouldn't result in a new configuration being fetched unless more than an hour has passed or the user previously killed the app.

To access our configuration parameter values, we implement getters for the relevant properties in the PAWConfigManager. Remember that PFConfig returns a dictionary based on the parameters stored on Parse and this is stored in the config property. The search radius options can be retrieved through the availableFilterDistances key. We'll define a filterDistanceOptions property is to hold the result of this retrieving this data:

// PAWConfigManager.m

- (NSArray *)filterDistanceOptions {
    NSMutableArray *distanceOptions = [self.config[@"availableFilterDistances"] mutableCopy];
    if (!distanceOptions) {
        // No config value, fall back to the defaults
        distanceOptions = [@[ @(250.0), @(2000.0), @(5000.0) ] mutableCopy];
    }
    return [distanceOptions copy];
}

If the config does not have the availableFilterDistances key, we return a default array of search radius options.

Similarly, the postMaxCharacterCount property of the PAWConfigManager is used to hold the configuration data for the postMaxCharacterCount key. It returns a default value if no data is found:

// PAWConfigManager.m

- (NSUInteger)postMaxCharacterCount {
    NSNumber *number = self.config[@"postMaxCharacterCount"];
    if (number == nil) {
        // No config value, fall back to the defaults
        return 140;
    }
    return [number unsignedIntegerValue];
}

6.3. Using the Configuration Data

Setting up the PAWConfigManager as a singleton makes it very easy to access the desired configuration data from anywhere in the app.

First, let's look at the PAWSettingsViewController that makes use of the filterDistanceOptions property. We set up a method that gets the desired data from the PAWConfigManager and adds the default search distance if it isn't in the list.

// PAWSettingsViewController.m

- (void)loadAvailableDistanceOptions {
    NSMutableArray *distanceOptions = [[[PAWConfigManager sharedManager]
                                        filterDistanceOptions] mutableCopy];
    NSNumber *defaultFilterDistance = @(PAWDefaultFilterDistance);
    if (![distanceOptions containsObject:defaultFilterDistance]) {
        [distanceOptions addObject:defaultFilterDistance];
    }

    [distanceOptions sortUsingSelector:@selector(compare:)];

    self.distanceOptions = [distanceOptions copy];
}

This method is called when the view controller is initialized. The distanceOptions property is then available to help set up the display.

Next up is the PAWWallPostCreateViewController that makes use of the postMaxCharacterCount property. We retrieve this data in the initialization method for the view controller:

// PAWWallPostCreateViewController.m

- (instancetype)initWithNibName:(NSString *)nibNameOrNil
                         bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        _maximumCharacterCount =
        [[PAWConfigManager sharedManager] postMaxCharacterCount];
    }
    return self;
}

Once the view controller is initialized, the maximumCharacterCount property can be used to display the maximum count and validate the data entered.

6.4 Wrapping Up

In this section of the tutorial, we saw how we could remotely manage Anywall's parameters. We used PFConfig to manage the server fetch, made use of locally cached configuration data, and handled scenarios where there was no configuration data available.