Posting Links to Facebook Profile from iPhone Code

Posted by Markus Piipari Wed, 19 Aug 2009 18:56:00 GMT

Recently, we needed to implement Facebook link sharing in one of our applications, to let users of the app who really like it let others now about it, too. Read on to find out how this can be done!

(And if you have the stamina, see the very end for a little discussion about how it is often too easy to overdo simple things...)

The Implementation

While the code is finally relatively simple, there are quite a number of steps to get there.

First, you need to sidetrack to Dan Grigsby's tutorial on Mobile Orchard about setting a Facebook users's status from iPhone code. The tutorial covers all necessary pre-requisites:

  • Setting up a Facebook application
  • Downloading the Facebook Connect SDK for iPhone
  • Setting up your Xcode project for it
  • Helper classes and examples you can base your own work on, as we did in the following.

Note that to succesfully follow the tutorial, you'll need to have a Git client installed, as the tutorial's helper classes are downloaded from GitHub.

Next, coding time!

Quick Glance at the Facebook Connect API and MOFBHelper

Before our first Objective-C line, here's the Facebook API we're going to use: Links.post

Don't be alarmed by the number of request parameters. Everything in the request except for the link's URL and comment text will be handled by the Facebook Connect package and MOFBHelper (and the other original helper classes).

Step 1: Posting the Link

Let's start from the bottom - the code doing the actual work - and work our way towards the top, the user interface.

Because we don't want to modify the Mobile Orchard code in any way, let's create a set of helper classes of our own.

Here is the interface for PCFacebookLinks, a class that posts links to the Facebook profile. The structure of the code is quite similar to MOFBStatus, the original helper class that does the same thing with setting a user's Facebook status.

	#import <Foundation/Foundation.h>
	#import "FBConnect/FBConnect.h"

	#import "MOFBPermission.h"

	@class PCFacebookLinks;

	@protocol PCFacebookLinksDelegate <NSObject>

	@optional
	-(void)linkWasPosted:(id)links;
	-(void)linkPosting:(PCFacebookLinks*)links didFailWithError:(NSError*)error;
	@end

	@interface PCFacebookLinks : NSObject <FBRequestDelegate, MOFBPermissionDelegate> {
		id _delegate;
		NSString *_comment;
		NSString *_url;
	}

	@property (nonatomic,assign) id <PCFacebookLinksDelegate> delegate;
	@property (nonatomic,retain) NSString *url;
	@property (nonatomic,retain) NSString *comment;

	- (void)postWithURL: (NSString *)url comment: (NSString *)comment;

	@end

And here's the implementation of PCFacebookLinks.

	#import "PCFacebookLinks.h"
	#import "MOFBErrors.h"

	@implementation PCFacebookLinks

	@synthesize delegate=_delegate, comment=_comment, url=_url;

	- (void)postWithURL: (NSString *)url
				comment: (NSString *)comment
	{
		_url = url;
		_comment = comment;
		MOFBPermission *permission = [[[MOFBPermission alloc] init] retain];
		permission.delegate = self;
		[permission obtain:@"share_item"];
	}

	- (void)permissionGranted:(MOFBPermission*)permission {
		[permission release];
		NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys: self.comment, @"comment", self.url, @"url", nil];
		[[FBRequest requestWithDelegate:self] call:@"facebook.Links.post" params:params];
	}

	- (void)permissionDenied:(MOFBPermission*)permission {
		[permission release];
		NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"Permission denied", NSLocalizedDescriptionKey, nil];
		NSError *error = [NSError errorWithDomain:@"PCFacebookErrorDomain" code:PERMISSION_DENIED userInfo:userInfo];
		[_delegate linkPosting:self didFailWithError:error];
	}

	- (void)request:(FBRequest*)request didLoad:(NSString *)result {
		[_delegate linkWasPosted:self];
	}

	- (void)request:(FBRequest*)request didFailWithError:(NSError*)error {
		[_delegate linkPosting:self didFailWithError:error]; 
	}

	- (void)dealloc {
		[_url release];
		[_comment release];
		[super dealloc];
	}

	@end

Notice the asynchronouus nature of the code: first we ask for the share_item permission, and only in response to the permission being granted try and post the link.

Step 2: Hiding the Ugly Details

Next, we need to extend the original MOFBHelper to support links sharing in addition to status updates. Again, we don't want to touch the original helper classes, in order to keep things tidy, and our own code separate. Here's the interface, PCFacebookHelper.

    #import <Foundation/Foundation.h>
	#import "MOFBHelper.h"

	#import "PCFacebookLinks.h"

	@class PCFacebookHelper;

	@protocol PCFacebookHelperDelegate <MOFBHelperDelegate>

	-(void)linkWasPosted:(id)helper url:(NSString*)url comment:(NSString*)comment;
	-(void)linkPosting:(id)helper didFailWithError:(NSError*)error;
	@end

	@interface PCFacebookHelper : MOFBHelper <PCFacebookLinksDelegate> {
		PCFacebookLinks *_links;
	}

	- (void)postLinkWithURL:(NSString *)url comment:(NSString *)comment;

	@end

...and its implementation:

    #import "PCFacebookHelper.h"

	@implementation PCFacebookHelper

	- (id) init {
		self = [super init];
		if(self != nil) {
			_links = [[[PCFacebookLinks alloc] init] retain];
			_links.delegate = self;
		}
		return self;
	}

	- (void)postLinkWithURL:(NSString *)url comment:(NSString *)comment
	{
		[_links postWithURL:url comment:comment];
	}

	- (void)linkWasPosted:(id)links {
		[delegate linkWasPosted:self url:[links url] comment:[links comment]];
	}

	-(void)linkPosting:(id)links didFailWithError:(NSError*)error {
		[delegate linkPosting:self didFailWithError:error];
	}

	- (void)dealloc {
		[_links release];
		[super dealloc];
	}

	@end

Step 3: User Interface Integration

Finally, we need to tie this code to the application's UI to test the thing. We'll implement a view controller for this purpose, PCFacebookViewController. (We're assuming here that you know what to do with a view controller, to display stuff in an iPhone UI.)

	#import <UIKit/UIKit.h>
	#import "FBConnect/FBConnect.h"

	#import "PCFacebookHelper.h"

	@interface PCFacebookViewController : UIViewController <FBSessionDelegate, PCFacebookHelperDelegate> {
		FBSession *_session;
		PCFacebookHelper *_helper;
	}

	@end

Note: be sure to replace the sessionForApplication and secret arguments with the appropriate values for your Facebook application in the following implementation.

You will of course also want to change the link URL and comment text in session:didLogin. (Our actual application let's the user to edit the comment before posting, but that's out of the scope of this blog post.)

    #import "PCFacebookViewController.h"

	@implementation PCFacebookViewController

	- (void)viewDidLoad {
		[super viewDidLoad];

		_session = [FBSession sessionForApplication:@"REPLACE THIS ONE"
											 secret:@"AND THIS, TOO"

										   delegate:self];		

		FBLoginButton* button = [[[FBLoginButton alloc] init] autorelease];
		[self.view addSubview:button];

		_helper = [[PCFacebookHelper alloc] init];
		_helper.delegate = self;
	}

	- (void)viewDidAppear:(BOOL)animated {
		[super viewDidAppear:animated];
		[_session resume];

	}

	- (void)session:(FBSession*)session didLogin:(FBUID)uid {
		[_helper postLinkWithURL:@"http://www.pearcomp.com/" comment:@"This link was posted from my iPhone!"];
	}

	-(void)linkWasPosted:(id)helper url:(NSString*)url comment:(NSString*)comment {
		NSLog(@"Link %@ was posted on Facebook with comment '%@'", url, comment);
	}

	-(void)linkPosting:(id)helper didFailWithError:(NSError*)error {
		NSLog(@"Link posting failed: %@", [error description]);
	}

	- (void)dealloc {
		[_session release];
		[_helper release];
		[super dealloc];
	}

	@end

What Did We Just Do?

Okay, it works. Are we happy with it? Yes and no.

We created something reusable that we can from now on include in any iPhone application that needs Facebook integration. Adding further Facebook Connect API features to our helper classes should also be straightforward.

Faithfully complying with the tradition of reusable software design, we created a codebase that can be maintained and improved in a single place. Setting up that single place will require some extra work, though.

I guess that's precisely the thing: there is just so much code there for such a little thing! Two levels of delegation, six separate files, and so on. It's hard to escape the feeling that we let some unnecessary architecture astronautism enter our heads when doing this. How many Facebook features are you going to need in an iPhone application, after all? Not that many, ever.

Then again, spending even more time on simplifying the code would be just more overkill. So, let's accept it for what it is, write a blog post, and move on to better, greater and simpler things!


App Development Diary in TheAppleBlog

Posted by Markus Piipari Sun, 02 Aug 2009 15:13:00 GMT

In addition to our on-going client projects, at PearComp we have also, for a while now, been working on an iPhone game of our own, to be released sometime in the not-too-distant future, hopefully in a couple of months.

While there will certainly be posts about our game development experiences in this blog, some of our first steps (and missteps) have also already been documented, with enthused journalistic vigour, at TheAppleBlog's App Developer Diary.

This is due to us agreeing, after the game project's initial launch, to become guinea pigs in a real-life app design & development experiment, to be observed by one of TheAppleBlog's regular writers, Olly Farshi. The diary reports on any (interesting enough) obstacles we stumble upon, lessons we learn and little victories we hopefully achieve, when creating a fully functional mobile game for the iPhone platform.

Of course, it's also not only about us. Instead, our undertakings often serve as triggers for the series to provide a broader perspective on the issue at hand, with, for example, interviews with bigger players in the industry, and Mr. Farshi's own insight and previous experience in mobile game design.

So far, the following four installments have been published in the App Developer Diary:

Each post so far has been a good read, and therefore heartily recommended!

Naturally, you should also stay tuned for the next ones. Oh right, this is the Internet... I mean, subscribe to the RSS feed!


NSArray+UniqueObjects

Posted by matias Thu, 30 Jul 2009 19:22:00 GMT

Hello world! I've recently come across the situation where I want to get rid of duplicate values in an NSArray but maintain a specific sort order. After researching the Cocoa documentation I couldn't find a way to doing this with a single method call. So here's a category on NSArray that does exactly this! So if you ever come across the same problem of getting the unique members of an NSArray in a certain sorting order, the following code should help you out. Here's the header:
#import <Foundation/Foundation.h>


@interface NSArray (UniqueObjects)

-(NSArray*) uniqueObjects;

-(NSArray*) uniqueObjectsSortedUsingSelector: (SEL)comparator;

-(NSArray*) uniqueObjectsSortedUsingFunction: (NSInteger (*)(id, id, void *)) comparator  
                                     context: (id)context 
                                        hint: (id)hint;

-(NSArray*) uniqueObjectsSortedUsingFunction: (NSInteger (*)(id, id, void *)) comparator 
                                     context: (id)context;

-(NSArray*) uniqueObjectsSortedUsingSortDescriptors: (NSArray*) sortDescs;

@end

... and the implementation:
#import "NSArray+UniqueObjects.h"


@implementation NSArray (UniqueObjects)

-(NSArray*) uniqueObjects {
    NSSet *set = [[NSSet alloc] initWithArray: self];
    NSArray *vals = [set allObjects];
    [set release];
    return vals;
}


-(NSArray*) uniqueObjectsSortedUsingSelector: (SEL)comparator {
    NSSet *set = 
        [[NSSet alloc] initWithArray: self];
    NSArray *vals = 
        [[set allObjects] sortedArrayUsingSelector: comparator];
    [set release];
    return vals;
}


-(NSArray*) 
uniqueObjectsSortedUsingFunction: 
(NSInteger (*)(id, id, void *)) comparator 
context: (id)context 

hint: (id)hint {
    NSSet *set = [[NSSet alloc] initWithArray: self];
    NSArray *vals = 
        [[set allObjects] sortedArrayUsingFunction: comparator
                                           context: context 
                                              hint: hint];
    [set release];
    return vals;
}


-(NSArray*) 
uniqueObjectsSortedUsingFunction: 
(NSInteger (*)(id, id, void *)) comparator 
context: (id)context {
    NSSet *set = [[NSSet alloc] initWithArray: self];

    NSArray *vals = [[set allObjects] 
    sortedArrayUsingFunction:comparator context:context];
    [set release];
    return vals;
}


-(NSArray*) 
uniqueObjectsSortedUsingSortDescriptors: (NSArray*) sortDescs 
{
    NSSet *set = [[NSSet alloc] initWithArray: self];
    NSArray *vals = 
        [[set allObjects] sortedArrayUsingDescriptors:sortDescs];
    [set release];
    return vals;
}


@end

Quo Vadis, Apple?

Posted by Benjamin Schuster-Böckler Wed, 29 Jul 2009 15:55:00 GMT

This week has seen a new peak in the ongoing saga of rejected iPhone applications: The rejection of Google's Voice App and the removal of similar apps that had been available in the store for months has stirred up the iPhone developer community. Not that things were all happy-bunny before that, either: Bloodcurling stories of arbitrary and inconcistent app rejections abound.

We’ve had similar experiences ourselves: A client asked us to develop a promotional application that was meant to be shown off to customers and business partners. The app featured a promo video, a picture gallery with a slideshow and a browsable list of the customer’s’ offices around the globe, witch contact details that could be dialled or emailed directly.

This is essentially identical in features to currently available applications from Chanel, Les Petits or Ralph Lauren. Yet, inexplicably, Apple decided to reject our app:

"Thank you for submitting your application to the App Store. Unfortunately, your application, ****** 1.0, cannot be posted to the App Store because it contains minimal user functionality. Its sole purpose is marketing or promoting your product/service, and would not be appropriate for the App Store.

If you believe that you can add additional features that utilize iPhone functionality, we encourage you to do so and resubmit ****** 1.0 for review."

Stories such as this one are being circulated widely in blogs and over Twitter. Right now, the terms "AT&T", "Google Voice" and "Apple" are amongst the top 5 topics of the day on Twitter. This is not good news for Apple. It is said that history repeats itself: When Microsoft introduced compulsory software activation for Windows, it pushed a lot of people over the edge to eventually try something different. This is how I ended up buying my first Mac. Similarly, many iPhone developers are jumping ship.

But more importantly, it cemented the image of Microsoft as an evil empire of ignorance and greed. Apple needs to learn from the mistakes of others: Your public image is a very precious commodity, and statements such as "Apple Is Growing Rotten To The Core" are not pointing in the right direction.

However, there is another serious problem here, which is of a much more practical nature: Apple earns a lot of money with iPhone applications, and chances are the growth potential is still huge. However, as every marketplace, we developers need to be able to judge both the risk and potential profit of developing an application. Random rejections, and even retrospective removal of applications, tarnishes the trust that is necessary for developers to spend serious time and money on good applications. I understand that Apple feels invincible given the current circumstances: They reported 1.5 billion downloads from the app store, orders of magnitude more than any other mobile platform. Few people would deny that Apple created a great platform and extremely powerful development tools. But now, they need to keep up with the times, or they might just loose their advantage as quickly as they gained it.

If you’ve read thus far, I suggest you also have a look at this post by fellow iPhone developer Jeff Scott and support/copy/resend his open letter to Steve Jobs to fix the app review process.


Update:
Techchrunch just ran a story on popular Mac developers ditching their iPhone, getting a Palm Pre instead. Do you hear the bells ringing, Apple?

App ranks for all categories and all app stores

Posted by Benjamin Schuster-Böckler Fri, 03 Jul 2009 03:30:00 GMT

Every iPhone developer wants to know how his apps are doing. While Apple tells you about sales and traffic, you can't really compare your app to the competition. Although this information is available in iTunes in the form of a "ranking" of the top 100 applications in each category, there is no way of getting to it other than clicking through all the pages in iTunes by hand. What makes things worse is that there are many stores, and the rankings will be different in each store and category.

Not too long ago, Ben Chatelain wrote a perl script, based on Erica Sadun's previous work, to exctract the rankings for an app identified by its ID from any iTunes Application Store in the world. Unfortunately, it seems that Apple keeps changing the XML format that these web services return. The original Perl script by Ben was subsequently modified by the guys at touchcentric to make it faster and easier to use.

Last week I downloaded the perl script from their website and tried to get statistics for our new app, Point Don't Shout. As it turned out, the script didn't work for me. I traced it down to the regular expression that digests the XML returned by Apple and extracts an app's rank, id and name. It seems that Apple was kind enough to change their format, once again.

I set out to fixing this issue and modified the Perl script again. However, as the format has become more complicated, I decided to properly parse the XML using XML::TreeBuilder, a nice and simple XML DOM style parser for Perl. I also cleaned up the code and removed some unused functions as well as modifying the way caching was handled previously.

The new script can be downloaded here. Please let me know if you run into problems, or if you are overjoyed to use this little piece of code.

Releasing your first application

Posted by Benjamin Schuster-Böckler Sat, 13 Jun 2009 02:18:00 GMT

These weeks have been both manic and exciting for us. Manic because we had to rush to fix issues with the 3.0 beta compatibility, get additional translations of the user interface done, and deal with Apple's not always entirely intuitive submission process. Exciting on the other hand, because we finally managed to get our first application out of the door and onto the app store. In case you hadn't seen it on our Apps page, Point Don't Shout is a picture dictionary which helps you make yourself understood, even if you don't know how to pronounce a word in a foreign language. Currently it features more than 700 terms, and can translate between 8 different languages.
Once your app is eventually out there on the Store, you feel like a parent whose kids moved out to go to college. There's not much more you can do. They're on their own now! You can try and support them by helping with the move but in the end they have to find their own way. So while we are very excited about the release, one also feels a bit anxious, because it's now beyond us how the app will be taken up by users. I'm sure that many developers, and probably a lot of artists and writers will feel the same, and we'd love to hear about it. A worry shared is a worry halved.

Welcome to the new site!

Posted by matias Fri, 29 May 2009 16:59:00 GMT

Hello world! You’ll be hearing about our products and perhaps even some iPhone app development tips and tricks from here.

enter>