UIActivityViewController (Part 1): Tweeting and Printing in Swift


If you've read recent posts on this blog about the presentation of popovers then you'll be familiar with the approach of accessing a UIPopoverPresentationController. The only difference with UIActivityViewController is that there are fixed requirements for this class:
On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally.
But not only is this a requirement, it is a default. And this can be demonstrated to be true by placing the following code in the viewDidLoad method of a view controller and running:
let str = "String"
let activityController = UIActivityViewController(activityItems: [str], applicationActivities: nil)
switch activityController.modalPresentationStyle {
    case .FullScreen:
        print("Full")
    case .PageSheet:
        print("Page")
    case .FormSheet:
        print("Form")
    case .CurrentContext:
        print("Current")
    case .Custom:
        print("Custon")
    case .OverFullScreen:
        print("OverFull")
    case .OverCurrentContext:
        print("OverCurrent")
    case .Popover:
        print("Popover") // Popover
    case .None:
        print("None")
}
which means that we don't need to actually set the UIActivityViewController modalPresentationStyle at all, because it is already set to popover out of the box.

Sending your first tweet

Moving along, let's imagine the minimum amount of code we're going to need in order to share some text:
@IBAction func shareStuff(sender: AnyObject) {
    let activityController = UIActivityViewController(activityItems: ["Hello World!"], applicationActivities: nil)
    self.presentViewController(activityController, animated: true, completion: nil)
    let presCon = activityController.popoverPresentationController
    presCon?.barButtonItem = sender as! UIBarButtonItem
}
To implement this you simply need to hook up a Bar Button Item to the method, and when you build and run you'll receive a range of options that depend on your share settings.

In the simulator you might only see Mail and Copy but on a real device you should see a fuller set of options and there might well be some additional services (provided by extensions) that you can add in using More ... (for example, Dropbox). And if you use twitter then you should be able to click on Twitter and a tweet will appear.

Note: For brevity, I've force unwrapped optionals in the code. If you used this in a real app there would be a crash if any of the force unwrapped values turn out to be nil. So make sure you employ if-let and so on when using in an app.

Adding images

Quite often when we tweet we want to append an image to the tweet. This can be achieved in one of two ways, either using a UIImage:
@IBAction func shareStuff(sender: AnyObject) {
    let img = UIImage(named:"myImage.jpg")!
    let activityController = UIActivityViewController(activityItems: ["Hello World!",img], applicationActivities: nil)
    self.presentViewController(activityController, animated: true, completion: nil)
    let presCon = activityController.popoverPresentationController
    presCon?.barButtonItem = sender as! UIBarButtonItem
}
or using a NSURL:

@IBAction func shareStuff(sender: AnyObject) {
    let img = NSBundle.mainBundle().URLForResource("myImage", withExtension: "jpg")!
    let activityController = UIActivityViewController(activityItems: ["Hello World!",img], applicationActivities: nil)
    self.presentViewController(activityController, animated: true, completion: nil)
    let presCon = activityController.popoverPresentationController
    presCon?.barButtonItem = sender as! UIBarButtonItem
}
So you start to get the idea that we can add to the activityItems dictionary and the UIActivityController infers what it is that should be made available. So we can now send an email with the image as an attachment, Message with the picture attached, tweet with the picture attached, etc.
We can also do things with only one of the items, like save the image and do things like copy and paste the text to a text editor. In the former, the text will be discarded and in the latter the image will be discarded. Things won't fail just because an app cannot use all the assets provided.

Note: For a full list of objects that can be provided for any given activity type, see the built-in Activity Types reference.

Printing from a UIActivityViewController

You'll notice that having added a UIImage (or an image URL) that we now have the opportunity to Print. But it won't be the text that prints, it will be only the image. This is because a UIActivtyTypePrint doesn't accept a String, it accepts the following types: UIImage, NSURL, NSData, UIPrintPageRenderer, UIPrintFormatter, and UIPrintInfo.

A simple example of how we can create a UIPrintFormatter, for example, is shown here:
let img = UIImage(named:"myImage.jpg")!
let imgP = UIImageView(image: img).viewPrintFormatter()
Note: if we provide two images in the activityItems array then there will be two pages of images printed and so on.

Further note: if you experience problems using AirPrint (especially after updating to a new iOS version) then perform a reboot of the iPad and try again.

Excluding activities

But perhaps mixing the same content for tweeting with the content that you want to provide for printing isn't desirable and perhaps there's a risk that new extensions might mess up with certain combinations of objects. So we can exclude certain activities using code like this:
activityController.excludedActivityTypes = [UIActivityTypePrint]
The full code being:
@IBAction func shareStuff(sender: AnyObject) {
    let img = NSBundle.mainBundle().URLForResource("myImage", withExtension: "jpg")!
    let activityController = UIActivityViewController(activityItems: ["Hello World!",img], applicationActivities: nil)
    activityController.excludedActivityTypes = [UIActivityTypePrint]
    self.presentViewController(activityController, animated: true, completion: nil)
    let presCon = activityController.popoverPresentationController
    presCon?.barButtonItem = sender as! UIBarButtonItem
}
Adding to the exclusion list, it's possible to use any of the built in Activity Types. And if we exclude print from the action menu, it is then possible to create a separate button to call a UIPrintInteractionController popover or build a custom UIActivity.

UIPrintInteractionController

A basic UIPrintInteractionController might look something like this:
@IBAction func printStuff(sender: AnyObject) {
    if let printInteract = UIPrintInteractionController.sharedPrintController(),
    img = UIImage(named:"myImage.jpg")
       {
        let imgView = UIImageView(image: img)
        printInteract.printFormatter = imgView.viewPrintFormatter()
        printInteract.presentFromBarButtonItem(sender as! UIBarButtonItem, animated: true, completionHandler: nil)     
        }
}
It might also be that we actually want both text and image to be printed and so we then need to turn to the composition of the objects we are printing into a singular form, such as a PDF or single image, but I'll leave that for a future post.

Conclusion

The UIActivityViewController is designed to be extremely flexible and infer in any given situation what is required, but there can be different requirements when tweeting and printing, for example. This is why the exclusion list is useful and why behaviour needs to be tested.

Next time I'll take a closer look at how a custom UIActivity can be implemented (and we'll see what the applicationActivities parameter is actually for).

Comments