1.11. Presenting Custom Sharing Options with UIActivityViewController

Problem

You want your app to participate in the list of apps that can handle sharing in iOS and appear in the list of available activities displayed in the activity view controller (see Figure 1-27).

You may need something like this, for example, when you have a text-editing app and when the user presses the Share button, you want a custom item that says “Archive” to appear in the activity view controller. When the user presses the Archive button, the text inside your app’s editing area will get passed to your custom activity, and your activity can then archive that text into the filesystem on the iOS device.

Solution

Create a class of type UIActivity. In other words, subclass the aforementioned class and give a name (whatever you like) to your new class. Instances of the subclasses of this class can be passed to the initWithActivityItems:applicationActivities: initializer of the UIActivityViewController class, and if they implement all the required methods of the UIActivity class, iOS will display them in the activity view controller.

Discussion

The initWithActivityItems:applicationActivities: method’s first parameter accepts values of different types. These values can be strings, numbers, images, etc.—any object, really. When you present an activity controller with an array of arbitrary objects passed to the initWithActivityItems parameter, iOS will go through all the available system activities, like Facebook and Twitter, and will ask the user to pick an activity that suits her needs best. After the user picks an activity, iOS will pass the type of the objects in your array to the registered system activity that the user picked. Those activities can then check the type of the objects you are trying to share and decide whether they can handle those objects or not. They communicate this to iOS through a specific method that they will implement in their classes.

So let’s say that we want to create an activity that can reverse any number of strings that are handed to it. Remember that when your app initializes the activity view controller through the initWithActivityItems:applicationActivities: method, it can pass an array of arbitrary objects to the first parameter of this method. So our activity is going to peek at all these objects in this arbitrary array, and if they are all strings, it is going to reverse them and then display all the reversed strings in an alert view.

  1. Subclass UIActivity as shown here:

#import <UIKit/UIKit.h>

@interface StringReverserActivity : UIActivity

@end
  1. Since our activity is going to be responsible for displaying an alert view to the user when an array of strings is passed to us, we need to ensure that our activity conforms to the UIAlertViewDelegate protocol and marks our activity as “finished” when the user dismisses the alert view, like so:

#import "StringReverserActivity.h"

@interface StringReverserActivity () <UIAlertViewDelegate>
@property (nonatomic, strong) NSArray *activityItems;
@end

@implementation StringReverserActivity

- (void)            alertView:(UIAlertView *)alertView
    didDismissWithButtonIndex:(NSInteger)buttonIndex{
    [self activityDidFinish:YES];
}
  1. Next, override the activityType method of your activity. The return value of this method is an object of type NSString that is a unique identifier of your activity. This value will not be displayed to the user and is just for iOS to keep track of your activity’s identifier. There are no specific values that you are asked to return from this method and no guidelines available from Apple, but we will follow the reverse-domain string format and use our app’s bundle identifier and append the name of our class to the end of it. So if our bundle identifier is equal to com.pixolity.ios.cookbook.myapp and our class name is StringReverserActivity, we will return com.pixolity.ios.cookbook.myapp.StringReverserActivity from this method, like so:

- (NSString *) activityType{
    return [[NSBundle mainBundle].bundleIdentifier
            stringByAppendingFormat:@".%@", NSStringFromClass([self class])];
}
  1. The next method to override is the activityTitle method, which should return a string to be displayed to the user in the activity view controller. Make sure this string is short enough to fit into the activity view controller:

- (NSString *) activityTitle{
    return @"Reverse String";
}
  1. The next method is activityImage, which has to return an instance of UIImage that gets displayed in the activity view controller. Make sure that you provide both Retina and non-Retina versions of the image for both iPad and iPhone/iPod. The iPad Retina image has to be 110×110 pixels and the iPhone Retina image has to be 86×86 pixels. Obviously, divide these dimensions by 2 to get the width and the height of the non-Retina images. iOS uses only the alpha channel in this image, so make sure your image’s background is transparent and that you illustrate your image with the color white or the color black. I have already created an image in my app’s image assets section, and I’ve named the image “Reverse,” as you can see in Figure 1-29. Here is our code, then:

- (UIImage *) activityImage{
    return [UIImage imageNamed:@"Reverse"];
}
Our asset category contains images for our custom activity

Figure 1-29. Our asset category contains images for our custom activity

  1. Implement the canPerformWithActivityItems: method of your activity. This method’s parameter is an array that will be set when an array of activity items is passed to the initializer of the activity view controller. Remember, these are objects of arbitrary type. The return value of your method will be a Boolean indicating whether you can perform your actions on any of the given items or not. For instance, our activity can reverse any number of strings that it is given. So if we find one string in the array, that is good enough for us because we know we will later be able to reverse that string. If we are given an array of 1,000 objects that contains only 2 strings, we will still accept it. But if we are given an array of 1,000 objects, none of which are of our acceptable type, we will reject this request by returning NO from this method:

- (BOOL) canPerformWithActivityItems:(NSArray *)activityItems{

    for (id object in activityItems){
        if ([object isKindOfClass:[NSString class]]){
            return YES;
        }
    }

    return NO;

}
  1. Now implement the prepareWithActivityItems: method of your activity, whose parameter is of type NSArray. This method gets called if you returned YES from the canPerformWithActivityItems: method. You have to retain the given array for later use. You don’t really actually have to retain the whole array. You may choose to retain only the objects that you need in this array, such as the string objects.

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

    NSMutableArray *stringObjects = [[NSMutableArray alloc] init];
    for (id object in activityItems){
        if ([object isKindOfClass:[NSString class]]){
            [stringObjects addObject:object];
        }
    }

    self.activityItems = [stringObjects copy];
}
  1. Last but not least, you need to implement the performActivity method of your activity, which gets called when iOS wants you to actually perform your actions on the list of previously-provided arbitrary objects. In this method, basically, you have to perform your work. In our activity, we are going to go through the array of string objects that we extracted from this arbitrary array, reverse all of them, and display them to the user using an alert view:

- (NSString *) reverseOfString:(NSString *)paramString{

    NSMutableString *reversed = [[NSMutableString alloc]
                                 initWithCapacity:paramString.length];

    for (NSInteger counter = paramString.length - 1;
         counter >= 0;
         counter--){
        [reversed appendFormat:@"%c", [paramString characterAtIndex:counter]];
    }

    return [reversed copy];

}

- (void) performActivity{

    NSMutableString *reversedStrings = [[NSMutableString alloc] init];

    for (NSString *string in self.activityItems){
        [reversedStrings appendString:[self reverseOfString:string]];
        [reversedStrings appendString:@"\n"];
    }

    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Reversed"
                                                        message:reversedStrings
                                                       delegate:self
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];

    [alertView show];

}

We are done with the implementation of our activity class. Now let’s go to our view controller’s implementation file and display the activity view controller with our custom activity in the list:

#import "ViewController.h"
#import "StringReverserActivity.h"

@implementation ViewController

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

    NSArray *itemsToShare = @[
                              @"Item 1",
                              @"Item 2",
                              @"Item 3",
                              ];

    UIActivityViewController *activity =
        [[UIActivityViewController alloc]
         initWithActivityItems:itemsToShare
         applicationActivities:@[[StringReverserActivity new]]];

    [self presentViewController:activity animated:YES completion:nil];
}
@end

When the app runs for the first time, you will see something similar to Figure 1-30 on the screen.

Our custom Reverse String activity is showing in the list of available activities

Figure 1-30. Our custom Reverse String activity is showing in the list of available activities

If you now tap on the Reverse String item in the list, you should see something similar to that shown in Figure 1-31.

Our string reverser activity in action

Figure 1-31. Our string reverser activity in action

See Also

Recipe 1.10

Get iOS 7 Programming Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.