How to share objects using your own UIActivity

Screen Shot 2015-10-22 at 17.48.09

We’ve discussed how to use a UIActivityViewController in my pervious article. In it I’ve mentioned that you can use our own activity items in addition to the built-in system sharing items (like email, Facebook, Twitter and so forth).

In this article I’ll show you how to do that.

A UIActivity item is an abstract class, which means that you can’t use it without subclassing it. That’s easily done in Xcode by heading over to File – New – Cocoa Touch Class. Give your class a name (I’ll call it MyActivity in this example) and select UIActivity as the subclass.

Screen Shot 2015-10-22 at 17.52.20

We’ll end up with two new files in our project that we’ll populate with code – and then we can start using it from our main view controller.

Overriding important properties and methods

In MyActivity.m file, I’ll add an array property to hold the potential items my activity could react to.

#import "MyActivity.h"

@interface MyActivity ()
@property (nonatomic, strong) NSArray *activityItems;
@end

Because the UIActivity class inherits directly from NSObject, there are several things we need to override for iOS to recognise our subclass as something usable. Here are the properties that are mandatory to override (as in “not optional”). The comments explain what each property does:

@implementation MyActivity

# pragma mark - properties and methods we must override

- (NSString *)activityType {
    
    // a unique identifier
    return @"com.versluis.custom-activity";
}

- (NSString *)activityTitle {
    
    // a title shown in the sharing menu
    return @"Custom Activity";
}

- (UIImage *)activityImage {
    
    // an image to go with our option
    return [UIImage imageNamed:@"construction"];
}

+ (UIActivityCategory)activityCategory {
    
    // which row our activity is shown in
    // top row is sharing, bottom row is action
    return UIActivityCategoryAction;
}

These are all about how your activity is known to and presented by the system.

Notice that the last one (activityCategory) is a class method – Xcode won’t auto-complete this one if you start it with a minus sign. You must use a plus sign instead. This property will add your activity either to the top row (UIActivityCategoryShare) or to the bottom row (as shown).

There are also two mandatory methods we must override:

- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
    
    // return YES for anything that our activity can deal with
    for (id item in activityItems) {
        
        // we can deal with strings and images
        if ([item isKindOfClass:[NSString class]] || [item isKindOfClass:[UIImage class]]) {
            return YES;
        }
    }
    // for everything else, return NO
    return NO;
}

- (void)prepareWithActivityItems:(NSArray *)activityItems {
    
    // anything we need to prepare, now's the chance
    // custom UI, long running calculations, etc
    
    // also: grab a reference to the objects our user wants to share/action
    self.activityItems = activityItems;
}

These do the heavy lifting and define what type of objects your activity can deal with. We only get one chance to grab a reference to the items our users want to share with us, so we’ll store them in self.activityItems to react to them later.

The above implementations are enough for the activity to work – but it won’t do anything useful just yet. There are two optional items we can override, the latter of which will action the items we were given earlier:

# pragma mark - optional methods we can override

- (UIViewController *)activityViewController {
    
    // return a custom UI if we need it,
    // or the standard activity view controller if we don't
    return nil;
}

- (void)performActivity {
    
    // the main thing our activity does
    
    // act upon each item here
    for (id item in self.activityItems) {
        NSLog(@"YEY - someone wants to use our activity!");
        NSLog(@"They used this object: %@", [item description]);
    }
    
    // notify iOS that we're done here
    // return YES if we were successful, or NO if we were not
    [self activityDidFinish:YES];
}

The activityViewController property can present a unique user interface if the built-in action sheet isn’t to your liking. It’s also possible to collect user input at this point if you need to. Return a view controller here if you like, otherwise return nil.

And as the name suggests, the performActivity method acts on the items and makes our activity useful. Anything goes here: change some text and share it as an email, do something to an image before opening it in another app, etc – the world is your oyster.

In this method, we’ll loop through all items in our array and act upon each one (I’m merely printing the item’s description as a log message, but you get the picture).

When we’re finished, we need to call the self activityDidFinish method to let iOS know that we’re done acting on those items. The one parameter we need to specify is either YES if we were successful, or NO if there was a problem.

Either way, iOS will dismiss the action sheet for us and return to the previous UI, before the activity view controller was called to present our activity.

Including our activity in a UIActivityViewController

Now that we have our own activity setup, let’s call it by presenting an activity view controller. In my main view controller, I’ll create a property to hold a reference to my activity. I’ll also create a custom initialiser.

Neither of these things are strictly necessary because there’s not a lot of setup we have to do here, but just in case this was a real world app, we may have to do a bit of extra work. Feel free to cut and paste:

#import "ViewController.h"
#import "MyActivity.h"

@interface ViewController ()
@property (nonatomic, strong) MyActivity *customActivity;
@end

// ...


- (MyActivity *)customActivity {
    
    if (!_customActivity) {
        _customActivity = [[MyActivity alloc]init];
        
        // any setup for our activity can be done here
    }
    return _customActivity;
}

If anyone asks for it, our customActivity object will be initialised. Now we can call it the usual way, as I’ve explained in my previous article:

- (IBAction)shareImage:(id)sender {
    
    // an image we want to share
    UIImage *image = [UIImage imageNamed:@"construction-hires"];
    NSArray *items = @[image];
    
    // create an array of custom activities
    NSArray *activities = @[self.customActivity];
    
    // configure an activity view controller
    UIActivityViewController *controller = [[UIActivityViewController alloc]initWithActivityItems:items applicationActivities:activities];
    
    // and present it
    [self presentViewController:controller animated:YES completion:nil];
}

In this example we want to share a UIImage: we’ll wrap it in an array, configure an activity view controller and present it. On iPad you’ll have to present this as a Popover of course.

The only new thing we see here is with the applicationActivities array:

previously we’ve defined nil, which means iOS will only bring up system services to share/action the object. But we can wrap our own activity (or activities even) in an array and pass that to the controller – in which case iOS will present our own activity in addition to the system built-in ones.

If you don’t want to see any of those, you must exclude them as I’ve described in my previous article.

Demo Project

I’ve got a working demo project on GitHub, with some dummy artwork courtesy of GraphicStock. All it does is create the custom activity and shares an NSString and a UIImage. Very simple yet effective:

Further Reading





Leave a Reply