Nov 11, 2010

Lights, Camera, ActionSheet!: Updating iOS ShareKit’s Twitter service to provide photo and message support together in a cohesive UI

ShareKit (http://getsharekit.com) is a popular, open-source, drop-in solution for implementing social sharing features in iOS applications. By following a few installation steps and adding a few lines of code in your project, an array of sharing features are available for users of your iPhone and iPad application(s).

Recently, while implementing sharing functionality for a client, I became frustrated with limitations in how Twitter sharing was implemented in ShareKit. Specifically, ShareKit provides support for sending text tweets and url’s (built in support for sending tweets with img.ly –linked images was recently added in version 0.21), but ShareKit does not yet have support for selecting/capturing a photo and sending it as part of a tweet.   One might even argue that that was not its purpose, as ShareKit comes across as more focused on the communication with the various sharing services than it is on the UI around it. However, thanks to ShareKit’s open-source nature and framework for extensibility, I was able to add this functionality without much fanfare, and still leverage the recently added img.ly support to link to that image.  An overview of an initial form of this implementation is outlined in this blog post.

As outlined in ShareKit’s documentation (http://getsharekit.com/docs/), to launch ShareKit, one creates an SHKItem to share, a SHKActionSheet with the item, and then defines how to display the SHKActionSheet to the user:

- (void)myButtonHandlerAction{

// Create the item to share (in this example, a text message)
NSString *someText = @"This is a blurb of text.";
SHKItem *item = [SHKItem text:someText];

// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];

// Display the action sheet
[actionSheet showFromToolbar:navigationController.toolbar];
}

Within the ShareKit framework, the favoriteSharersforType in SHK.m is called, which provides the types of ‘sharers’ (Facebook, Twitter, etc.) for the type (SHKShareType) of item (SHKItem) declared on ShareKit launch.    Personally, I find the delineation of types:

  • URL
  • Image
  • Text
  • Files

understandable from an individual item perspective but a challenge from a UI perspective.   Specifically, when, for example,  an iOS app contains a ‘Share’ button, which when tapped presents an action sheet with share services that can be leveraged, one may not always know at that point the share type they wish to use.   This clashes with ShareKit philosophically – where when launching ShareKit one is instructed to provide the share type.

For my use case, I chose to use the ‘Text’ type and add image support, both from the iOS device library and capture device (if available).   The user can then elect whether to use it or not from within the Twitter message view.

Updating Twitter Sharer for Photo Support

SKHTwitterForm, which is responsible for displaying the twitter form and sending its data is the proper location to provide photo tweeting support.   SHKTwitter initializes SHKTwitterForm by calling its initWithNibName method.   Within initWithNibName, the right bar navigation button item for sending the tweet is added to the  nav bar.  It is within this method that we’ll add an UI bar button with an image of a camera, which when selected will link to our photo capturing/selecting functionality.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
{
tools = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 150, 44.01)];
// create array to hold the camera and send buttons, which //get added to the toolbar
buttons = [[NSMutableArray alloc] initWithCapacity:2];

self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(cancel)];

//camera.png of ~25-30 sq. px
UIImage* image = [UIImage imageNamed:@"camera.png"];
CGRect frame = CGRectMake(0, 0, image.size.width, image.size.height);
cameraButton = [[UIButton alloc] initWithFrame:frame];
[cameraButton setBackgroundImage:image forState:UIControlStateNormal];
[cameraButton setShowsTouchWhenHighlighted:YES];
[cameraButton addTarget:self action:@selector(takePicture) forControlEvents:UIControlEventTouchDown];
UIBarButtonItem* cameraBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:cameraButton];
[buttons addObject:cameraBarButtonItem];

[buttons addObject:[[UIBarButtonItem alloc] initWithTitle:@"Tweet!"
style:UIBarButtonItemStyleDone
target:self
action:@selector(save)]];

// stick the buttons in the toolbar
[tools setItems:buttons animated:NO];

// and put the toolbar in the nav bar
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:tools];

[cameraBarButtonItem release];
[cameraButton release];
}

return self;
}
</p>

When the camera button is selected, the takePicture method is called, which for this example shows a UIActionSheet that allows the user to choose whether they wish to leverage an image from their photo album or from their device’s camera (if available):

- (void)takePicture
{
UIActionSheet *popupSheet = [[UIActionSheet alloc]
initWithTitle:nil
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Take a Photo", @"Upload from Library", nil];

popupSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque;
[popupSheet showInView:self.view];
[popupSheet release];
}

As part of these changes, SHKTwitterForm now acts as a UIActionSheet delegate (UIActionSheetDelegate) and UIImagePickerController delegate (UIImagePickerControllerDelegate).  When a button on the above action sheet is selected, the UIActionSheetDelegate method clickedButtonAtIndex is called:

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSLog(@"actionSheet:clickedButtonAtIndex:%d", buttonIndex);
if(buttonIndex == 2) {
// cancel
//TODO: custom cancel code if appropriate
} else {
UIImagePickerController *imagePickerController = [[[UIImagePickerController alloc] init] autorelease];
imagePickerController.allowsEditing = YES;
if (buttonIndex == 0)
{
// Take a photo
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
} else if (buttonIndex == 1) {
// Upload from Library
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == NO)
imagePickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[self presentModalViewController:imagePickerController animated:YES];
}
}
</p>

The above code presents standard controls for selecting/taking the appropriate image, and again, our appropriate delegate method is called on completion of selection:

- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info {

NSLog(@"imagePickerController::didFinishPickingMediaWithInfo:%@", info);
[picker dismissModalViewControllerAnimated:YES];
itemImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
// hard-coded scaling using a custom category
UIImage *scaledImage = [itemImage scaleToSize:CGSizeMake(200.0f, 200.0f)];
itemImage = scaledImage;
[itemImage retain];
[scaledImage release];

//replace cameraButton with our image
[cameraButton setBackgroundImage:itemImage forState:UIControlStateNormal];
[cameraButton setShowsTouchWhenHighlighted:YES];

//allow selection of different image
[cameraButton addTarget:self action:@selector(takePicture) forControlEvents:UIControlEventTouchDown];
UIBarButtonItem* cameraBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:cameraButton];

//replace camera button with our custom image button
[buttons replaceObjectAtIndex:0 withObject:cameraBarButtonItem];

// stick the buttons in the toolbar
[tools setItems:buttons animated:NO];

// and put the toolbar in the nav bar
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:tools];

[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}
</p>

At this point, we have selected our image, had it placed in the ShareKit twitter toolbar, and if desired, entered our tweet text.   Now, it is important to ensure that we have set the proper type of Twitter message to ensure ShareKit completes the Twitter sharing flow properly.

As part of SHKTwitterForm’s save method, a call is made to SHKTwitter’s sendForm method.  This is an easy, quick place to make our change to allow us to send the proper type of Twitter message using built-in ShareKit controls.  A revised sendForm method might look like:

- (void)sendForm:(SHKTwitterForm *)form
{
if (form.itemImage) {
item.shareType = SHKShareTypeImage;
item.image = form.itemImage;
}

[item setCustomValue:form.textView.text forKey:@"status"];
[self tryToSend];
}

Here, we have checked to see if we have an itemImage defined, and if so, we change the item type to image (from text, which was previously chosen), and set the image for that item to match that from our form.  Otherwise, text processing is left to proceed normally.  That’s all we need to do – we can let ShareKit do the rest!  The rest of the twitter message flow within ShareKit will occur normally, and our image will be linked via an img.ly serviced link at the end of our ‘tweet’!

ShareKit is a great framework for reducing the code iOS developers need to write to leverage ‘Sharing’ services such as Twitter and Facebook.  Once developers start working with ShareKit in their own project, they are sure to find that there are some limitations that they wish were addressed by ShareKit.   Instead of complaining about and waiting for the perceived issue to be addressed, developers can take matters into their own hands thanks to the open-source, drop-in nature of ShareKit, and implement the functionality they need.   Whether it is allowing tweets with images and texts within a familiar UI, or something more complex (or novel!), ShareKit allows developers to focus on their particular use case and not get too deep into the plumbing of how it works.

About the Author

Object Partners profile.
Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Android Development for iOS Developers
Android development has greatly improved since the early days. Maybe you tried it out when Android development was done in Eclipse, emulators were slow and buggy, and Java was the required language. Things have changed […]
Add a custom object to your Liquibase diff
Adding a custom object to your liquibase diff is a pretty simple two step process. Create an implementation of DatabaseObject Create an implementation of SnapshotGenerator In my case I wanted to add tracking of Stored […]
Keeping Secrets Out of Terraform State
There are many instances where you will want to create resources via Terraform with secrets that you just don’t want anyone to see. These could be IAM credentials, certificates, RDS DB credentials, etc. One problem […]
Validating Terraform Plans using Open Policy Agent
When developing infrastructure as code using terraform, it can be difficult to test and validate changes without executing the code against a real environment. The feedback loop between writing a line of code and understanding […]