Since iOS 6 it has been really easy to share many complex objects, thanks to the UIActivityViewController. All we have to do is wrap our object (or objects) in an array, give that to the activity view controller, and present it.
To share a UIImage on iPhone for example, we’ll do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (IBAction)shareImageDefault:(id)sender { // grab an item we want to share UIImage *image = [UIImage imageNamed:@"three"]; NSArray *items = @[image]; // build an activity view controller UIActivityViewController *controller = [[UIActivityViewController alloc]initWithActivityItems:items applicationActivities:nil]; // and present it [self presentViewController:controller animated:YES completion:^{ // executes after the user selects something }]; } |
We need the array because we could be sharing more than just one item. The result looks something like the screenshot above: two rows of shareable icons appear, the top one representing “share” options, and the bottom one representing “action” options. Each of these is a UIActivity object. If ever you need to create your own activities, you can add them to either top or bottom category (but we won’t cover how to do that here).
On iPad, this type of presentation will crash – and Apple would like us to use a Popover presentation instead. It gets a bit long to show this here every time, so I’ve refactored this call into its own method and will be referring to it going forward:
1 2 3 4 5 6 7 8 9 |
- (void)presentActivityController:(UIActivityViewController *)controller { // for iPad: make the presentation a Popover controller.modalPresentationStyle = UIModalPresentationPopover; [self presentViewController:controller animated:YES completion:nil]; UIPopoverPresentationController *popController = [controller popoverPresentationController]; popController.permittedArrowDirections = UIPopoverArrowDirectionAny; popController.barButtonItem = self.navigationItem.leftBarButtonItem; |
Excluding certain activities
If called as mentioned above, iOS will compile a list of all possible activities our item can be shared as. UIImages for example could be saved to our Photo Library, Air-dropped, Facebooked or emailed, among other things.
To trim this list down to something more relevant, we can exclude certain activities. Imagine we don’t want the option to share on Facebook and Twitter, we can do it like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (IBAction)shareImageExcludeSocial:(id)sender { // grab an item we want to share UIImage *image = [UIImage imageNamed:@"three"]; NSArray *items = @[image]; // build an activity view controller UIActivityViewController *controller = [[UIActivityViewController alloc]initWithActivityItems:items applicationActivities:nil]; // exclude several items NSArray *excluded = @[UIActivityTypePostToFacebook, UIActivityTypePostToTwitter]; controller.excludedActivityTypes = excluded; // and present it [self presentActivityController:controller]; } |
The UIActivity class has a type property, each of which is an NSString, and Xcode knows them all. You can literally exclude anything and everything from this list (check the class reference below for all types you can include here).
Reacting to user selections
The UIActivityViewController calls a completion handler block we can access. In it we’re given the activity the user selected (as reverse-domain string), a BOOL ti indicate if the user used a sharing option (YES) or if the cancel button was pressed (NO), and error object and possible returned objects.
Sadly the Apple documentation isn’t very obvious how to do this at the time of writing, and it took some tinkering to find this out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// access the completion handler controller.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *error){ // react to the completion if (completed) { // user shared an item NSLog(@"We used activity type%@", activityType); } else { // user cancelled NSLog(@"We didn't want to share anything after all."); } if (error) { NSLog(@"An Error occured: %@, %@", error.localizedDescription, error.localizedFailureReason); } }; |
In essence, we need to create our own block through which the above objects are available, and add it to our controller. It’s slightly different from using a block that’s part of a method signature or implementing delegate methods – but this approach works just the same.
The activityType will be something like typecom.apple.UIKit.activity.SaveToCameraRoll, through which you can test which activity was selected by the user. If the completed BOOL returns NO, the cancel button was pressed.
Demo Project
I’ve got a working demo project on GitHub. Feel free to take a look at it:
Further Reading
- https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIActivityViewController_Class/index.html
- http://stackoverflow.com/questions/26425077/uiactivityviewcontroller-completion-handler-is-completed-when-using-airdrop
- http://stackoverflow.com/questions/12552414/uiactivityviewcontrollercompletionhandler-how-to
- https://gist.github.com/aaronwardle/5854403
Again you just saved me hours of work. Great work.
Excellent, great to hear!
Well presented. One thing I’m a problem with is sharing UIImages through email on the iPad. Everything else works fine. But specifically sharing over email on an iPad makes it just return with “completed” set to NO as soon as user taps the Mail icon. Very strange that I can’t seem to find any mention off it by anyone else.
Hy,
Using Xcode 8.3.3 and iPad (10.3.3), the image sharing does not work at all (there is just no image .. even not a blank or corrupted image ..)
If you have any idea ?
Anyway, thank you for your code !
Tux
Hi Tux,
I did some investigating, and the demo code runs fine as is on my iOS 9 device, sharing an image to the camera roll without a hitch. On iOS 10 however, I got a crash with the following explanation:
After fixing that, it shared the image as it should. I’ve explained how to add this key here: http://pinkstone.co.uk/what-is-the-nsphotolibraryusagedescription-key/.
I’ve also updated my Demo Project to include this key now, it should work fine with iOS 10 now.
Thank you for the article, let me complete the sharing feature quickly 😀
I got the error:-
No visible @interface for ‘XXXX’ declares the selector ‘presentViewController:animated:completion:’
Hi Ali, it’s difficult to diagnose this without seeing your code, but this error generally indicates that the class you’re calling this method on is not a child of the UIViewController class. For example, if I inherit my class from NSObject and call presentViewController:animated:completion on it, that error springs up (because my class doesn’t know that method, unless I define it myself, or unless the parent class has defined it). It’s possible that you’re calling the method on self, and self isn’t a view controller.