Hacking the default Facebook UIActivity

Introduction

UIActivity has been introduced in iOS 6.0 and one of its purposes was making social sharing easier.

In most cases, you wouldn’t need the TWTweetComposeViewController or SLComposeViewController anymore to send a tweet or post a status on Facebook (or integrating the Facebook SDK).

Instead, you would just create a UIActivityViewController, passing it some content to share (image, url and/or text) and you are presented with the standard system share picker, with every available option.

4 lines of code, no external SDK.

1
2
3
4
5
NSArray *activityItems = @[@"Awesome blog!",
                           [NSURL URLWithString:@"http://blog.jldagon.me"]];
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems
                                                                                         applicationActivities:nil];
[self presentViewController:activityVC animated:YES completion:nil];

Now, if you choose to share this content on Facebook, the standard share sheet is presented. You don’t need the Facebook SDK with a Facebook app id, you don’t need to have FB login.

And voilà. The text and URL have been shared on your Facebook profile!

But—there’s always a but—…

What if you would like the publication to be attributed to your Facebook app?

All contents shared with a UIActivityViewController are attributed to iOS. You can see it highlighted in the above screenshot.
That means no metrics, no measure of engagement through Facebook insights…

Facebook itself lists the disadvantages of using this standard component:

  • Share sheet does not support the complete platform attachment model
  • Can only be used if people using your app log in to Facebook via iOS 6
  • Does not support tagging friends
  • Posts from the share sheet are attributed to iOS

Point #2 can be problematic too.

What if the users have the Facebook app installed, but they haven’t linked their FB account in the iOS settings?

Facebook does not even appear as an option to share the content. Bummer.

Solving the problem

To fix these issues, you could roll your own social sharing component, handling Twitter, iMessage, Mail, Facebook and whatever other destination you wish. And it’s a great idea, especially if you want to customize the UI and have a complete control over the entire sharing process.

Or, if you’re willing to resort to some hackery, you could modify the behavior of the default Facebook UIActivity.

Disclaimer: That’s probably a bad idea, and could lead to rejections from the review team. I wouldn’t risk it for a contract work for example.

Is it be possible to use the Share Dialog of the Facebook app when we press the Facebook button?

Well, yes!

All we need is to find a way to intercept the tap on the Facebook button and replace the standard behavior by our custom code.

Each item presented by the UIActivityViewController is tied to a UIActivity object, describing what types of items it can share and how it can share them:

(from Apple documentation)

1
- (NSString *)activityType

A string that identifies the provided service to your app.

1
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems;

Returns a Boolean indicating whether the service can act on the specified data items.

1
- (void)prepareWithActivityItems:(NSArray *)activityItems

Prepares your service to act on the specified data.

1
- (void)performActivity;

Performs the service when no custom view controller is provided.

1
- (void)activityDidFinish:(BOOL)completed

Notifies the system that your activity object has completed its work.

We need to:

  1. identify the Facebook activity’s activityType
  2. somehow override canPerformWithActivityItems: to make it return YES when there’s no Facebook account linked in the iOS settings, but the Facebook app is installed
  3. plug ourselves into -prepareWithActivityItems: and prepare the data we’re going to feed the Facebook app with
  4. launch the share dialog in -performActivity
  5. call -activityDidFinish: when the content has been shared

I initially tried to swizzle -prepareWithActivityItems: on UIActivity to see what was going on and get the activity type.

Except that it wasn’t being called at all. I realized that it was because everything happens in a subclass of UIActivity. A private subclass.

So in order to do all the swizzling, I had to find what subclass was used. Digging through the UIKit runtime headers, I finally hit UISocialActivity (header here).

  1. This time it worked, methods were being called. Facebook’s activityType is com.apple.UIKit.activity.PostToFacebook.

  2. Enabling Facebook activity with no FB account set up

1
2
3
4
5
6
7
8
- (BOOL)cca_canPerformWithActivityItems:(NSArray *)activityItems
{
    if (/* we're in the Facebook activity and the Facebook app is here */) {
        return YES;
    }
    // Other activities or no Facebook app
    return [self cca_canPerformWithActivityItems:activityItems];
}
  1. In the swizzled -prepareWithActivityItems:, we’re retrieving the URL (if any), image(s) and text passed as activity items. Then we store a FBLinkShareParams or FBPhotosParam depending on what data is shared.

  2. Displaying the app through Facebook’s FBDialogs with the *Params object we created in step 3.

  3. When the Facebook app returns to our app, it calls the share completion handler in which we’re calling activityDidFinish:.

And there we are with a correctly—attributed publication:

This solution requires the Facebook SDK, so you must have a well-configured Facebook app, indicate the FacebookAppID and FacebookDisplayName in your Info.plist as well as the fb{app id} url scheme.

The code is available on GitHub: CCAFacebookAppActivity

– I’m @Jilouc on Twitter if you have any comment.