Integrating Facebook and Parse Tutorial: Part 2

In part 2 of our Parse + Facebook tutorial series, you’ll wrap up the image sharing app with image wall and comments features. By Toby Stephens.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Asynchronous Downloads and Notifications

Since the image is downloaded asynchronously from Parse using your shared NSOperationQueue, you cannot guarantee that the image will be downloaded before you render the table view. To get around this, you need to inform the table view when the image has been downloaded using NSNotificationCenter.

At the top of Comms.h, declare the following constant:

extern NSString * const N_ImageDownloaded;

Then, at the top of Comms.m, define the constant:

NSString * const N_ImageDownloaded = @"N_ImageDownloaded";

Still in Comms.m, go to the section in getWallImagesSince:forDelegate: where you use pffileOperationQueue to download the image from Parse. Add the following code to the end of this block operation:

// Notify - Image Downloaded from Parse
[[NSNotificationCenter defaultCenter] postNotificationName:N_ImageDownloaded object:nil];

This posts a notification to the notification center, informing all interested classes the image download is complete.

Open ImageWallViewController.m and add the following to the bottom of viewDidLoad:

// Listen for image downloads so that we can refresh the image wall
[[NSNotificationCenter defaultCenter] addObserver:self
	selector:@selector(imageDownloaded:)
	name:N_ImageDownloaded
	object:nil];

This lets NSNotificationCenter know that the ImageWallViewController class is interested in knowing when an image is downloaded from Parse.

Now add the following method to ImageWallViewController.m:

- (void) imageDownloaded:(NSNotification *)notification {
	[self.tableView reloadData];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

When an image has been downloaded from Parse, NSNotificationCenter issues the notification and invokes imageDownloaded: on ImageWallViewController which reloads the table view data.

As well, you remove ImageWallViewController from receiving notifications when it is deallocated so that NSNotificationCenter doesn’t try to send any messages to a destroyed object.

Build and run your project; login and open the Image Wall. This time, the table updates once the image has been downloaded and your image is now displayed:

ts_FBParse_imagewall2

Retrieving User Profile Pictures

Now that you have a way of downloading images asynchronously, simply repeat the process to retrieve the user’s Facebook profile picture.

Open up NSOperationQueue+SharedQueue.h and add the following static method definition:

+ (NSOperationQueue *) profilePictureOperationQueue;

Add the implementation to NSOperationQueue+SharedQueue.m:

+ (NSOperationQueue *) profilePictureOperationQueue {
	static NSOperationQueue *profilePictureQueue = nil;
	if (profilePictureQueue == nil) {
		profilePictureQueue = [[NSOperationQueue alloc] init];
		[profilePictureQueue setName:@"com.rwtutorial.profilepicturequeue"];
	}
	return profilePictureQueue;
}

Now, calling [NSOperationQueue profilePictureOperationQueue] will provide you with a shared NSOperationQueue for downloading Facebook profile pictures.

Open Comms.h and add the following code to the top of the file:

extern NSString * const N_ProfilePictureLoaded;

Open Comms.m and define the string at the top of the file:

NSString * const N_ProfilePictureLoaded = @"N_ProfilePictureLoaded";

The above code sets the notification name for downloaded profile pictures.

Still in Comms.m, add the following code to login: where you save the PFUser object and just before you add the user to the friends list in the DataStore:

// Launch another thread to handle the download of the user's Facebook profile picture
[[NSOperationQueue profilePictureOperationQueue] addOperationWithBlock:^ {
	// Build a profile picture URL from the user's Facebook user id
	NSString *profilePictureURL = [NSString stringWithFormat:@"https://graph.facebook.com/%@/picture", me.id];
	NSData *profilePictureData = [NSData dataWithContentsOfURL:[NSURL URLWithString:profilePictureURL]];
	UIImage *profilePicture = [UIImage imageWithData:profilePictureData];

	// Set the profile picture into the user object
	if (profilePicture) [me setObject:profilePicture forKey:@"fbProfilePicture"];
						
	// Notify that the profile picture has been downloaded, using NSNotificationCenter
	[[NSNotificationCenter defaultCenter] postNotificationName:N_ProfilePictureLoaded object:nil];
}];

The above code constructs a URL for the location of the profile picture and then downloads the file. It then sets the downloaded image as an object in the FBGraphUser object with a key @”fbProfilePicture”. Once the download is complete, a notification is sent to the notification center.

That takes care of your own profile picture; now it’s time to do the same for your friends.

Add the following code to login: in Comms.m where you loop through your friend list and add the following code before you add the friend to the friends list in the DataStore.

// Launch another thread to handle the download of the friend's Facebook profile picture
[[NSOperationQueue profilePictureOperationQueue] addOperationWithBlock:^ {
	// Build a profile picture URL from the friend's Facebook user id
	NSString *profilePictureURL = [NSString stringWithFormat:@"https://graph.facebook.com/%@/picture", friend.id];
	NSData *profilePictureData = [NSData dataWithContentsOfURL:[NSURL URLWithString:profilePictureURL]];
	UIImage *profilePicture = [UIImage imageWithData:profilePictureData];

	// Set the profile picture into the user object
	if (profilePicture) [friend setObject:profilePicture forKey:@"fbProfilePicture"];
								
	// Notify that the profile picture has been downloaded, using NSNotificationCenter
	[[NSNotificationCenter defaultCenter] postNotificationName:N_ProfilePictureLoaded object:nil];
}];

The above code looks quite similar; it does exactly the same thing as as you did for your own profile picture, but does it now for each of your Facebook friends.

Now that you have asynchronous loading of profile images, you need to update the Image Wall table view whenever a profile picture has downloaded.

Open ImageWallViewController.m and add the following code to the end of viewDidLoad:

// Listen for profile picture downloads so that we can refresh the image wall
[[NSNotificationCenter defaultCenter] addObserver:self
	selector:@selector(imageDownloaded:)
	name:N_ProfilePictureLoaded
	object:nil];

The selector imageDownloaded: is the same as the other notification for images downloaded from Parse, since the behavior is the same — it reloads the table data. There’s no sense in duplicating code!

The final step is to add the profile pictures to the section header cell in the table view for the Image Wall.

Add the following code to the bottom of tableView:viewForHeaderInSection: in ImageWallViewController.m just before you return the cell:

// Add the user's profile picture to the header cell
[imageCell.profilePicture setImage:wallImage.user[@"fbProfilePicture"]];

Build and run your project; this time you will see your lovely profile picture in the top right corner of the section header, as shown below:

ts_FBParse_imagewall3

Adding Comments to Images

Your app will be a lot more engaging once you allow the users to comment on each other’s images.

Add the following method declaration to Comms in Comms.h:

+ (void) getWallImageCommentsSince:(NSDate *)lastUpdate forDelegate:(id<CommsDelegate>)delegate;

Next, add the following callback method to the CommsDelegate protocol in Comms.h:

- (void) commsDidGetNewWallImageComments:(NSDate *)updated;

Now open Comms.m and add the following method:

+ (void) getWallImageCommentsSince:(NSDate *)lastUpdate forDelegate:(id<CommsDelegate>)delegate
{
	// Get all the Wall Image object Ids
	NSArray *wallImageObjectIds = [DataStore instance].wallImageMap.allKeys;
	
	// Execute the PFQuery to get the Wall Image Comments for all the Wall Images
	PFQuery *commentQuery = [PFQuery queryWithClassName:@"WallImageComment"];
	[commentQuery orderByAscending:@"createdAt"];
	[commentQuery whereKey:@"updatedAt" greaterThan:lastUpdate];
	[commentQuery whereKey:@"imageObjectId" containedIn:wallImageObjectIds];
	[commentQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
		// In the callback, we will return the latest update timestamp with this request.
		// Default to the current last update.
		__block NSDate *newLastUpdate = lastUpdate;
		
		if (error) {
			NSLog(@"Objects error: %@", error.localizedDescription);
		} else {
			[objects enumerateObjectsUsingBlock:^(PFObject *wallImageCommentObject, NSUInteger idx, BOOL *stop) {
				// Look up the User's Facebook Graph User
				NSDictionary<FBGraphUser> *user = [[DataStore instance].fbFriends objectForKey:wallImageCommentObject[@"userFBId"]];
				
				// 1
				// Look up the Wall Image
				WallImage *wallImage = [[DataStore instance].wallImageMap objectForKey:wallImageCommentObject[@"imageObjectId"]];
				
				// Add the Comment to the Wall Image
				if (wallImage) {
					WallImageComment *wallImageComment = [[WallImageComment alloc] init];
					wallImageComment.user = user;
					wallImageComment.createdDate = wallImageCommentObject.updatedAt;
					wallImageComment.comment = wallImageCommentObject[@"comment"];
					if ([wallImageCommentObject.updatedAt compare:newLastUpdate] == NSOrderedDescending) {
						newLastUpdate = wallImageCommentObject.updatedAt;
					}

					//2
					[wallImage.comments addObject:wallImageComment];
				}
			}];
		}
		
		// Callback
		if ([delegate respondsToSelector:@selector(commsDidGetNewWallImageComments:)]) {
			[delegate commsDidGetNewWallImageComments:newLastUpdate];
		}
	}];	
}

This should all look very familiar; it’s almost exactly the same as the getWallImagesSince: method, the only difference is that here you’re setting the comments.

The comments returned from Parse contain a reference to the WallImage objectid. You then use the objectid to look up the WallImage object that is associated with this comment. Once you’ve created the WallImageComment object, you then add it to the comments array of the WallImage

Again, all that’s left is to call this method from ImageWallViewController. Open ImageWallViewController.m and add a new NSDate timestamp to the ImageWallViewController class extension:

NSDate *_lastCommentUpdate;

This stores the timestamp of the latest comment to ensure that you don’t get any historic comments in your request to Parse.

Add the following line to viewDidLoad in ImageWallViewController.m to initialize the timestamp:

_lastCommentUpdate = [NSDate distantPast];

Still in ImageWallViewController.m, add the following line to commsDidGetNewWallImages:, just before you reload the table data:

// Get the latest WallImageComments from Parse
[Comms getWallImageCommentsSince:_lastCommentUpdate forDelegate:self];

Now, once the WallImages have been loaded you’ll fire off a request to get the latest comments.

Add the following method to ImageWallViewController.m:

- (void) commsDidGetNewWallImageComments:(NSDate *)updated {
	// Update the update timestamp
	_lastCommentUpdate = updated;

	// Refresh the image wall table
	[self.tableView reloadData];
}

Here, you update the _lastCommentUpdate timestamp with the new value returned from Parse, then reload the table view to show the comments.

Replace the contents of tableView:numberOfRowsInSection: in ImageWallViewController.m with the following:

// One row per WallImage comment
WallImage *wallImage = ([DataStore instance].wallImages[section]);
return wallImage.comments.count;

In your image wall, each of the sections of your table represents a single image, while each of the rows in that section represents a comment. In the above code, you return the number of rows for each section which is the number of comments on that particular image.

Add the following code to tableView:cellForRowAtIndexPath: in ImageWallViewController.m, at the point just before you return the cell:

// Get the WallImage from the indexPath.section
WallImage *wallImage = ([DataStore instance].wallImages[indexPath.section]);

// Get the associated WallImageComment from the indexPath.row
WallImageComment *wallImageComment = wallImage.comments[indexPath.row];
[commentCell.profilePicture setImage:wallImageComment.user[@"fbProfilePicture"]];
[commentCell.comment setText:wallImageComment.comment];

In the above code, you look up the particular WallImage from the indexPath.section to populate the comments cell. Next, you look up the WallImageComment from the indexPath.row, set the profile picture of the commenting user, and finally set the comment text.

Build and run your project; open up the Image Wall and you’ll see your comment below your image, as shown below:

ts_FBParse_imagewall4

Toby Stephens

Contributors

Toby Stephens

Author

Over 300 content creators. Join our team.