UIActivity​View​Controller

On iOS, UIActivityViewController provides a unified interface for users to share and perform actions on strings, images, URLs, and other items within an app.

You create a UIActivityViewController by passing in the items you want to share and any custom activities you want to support (we’ll show how to do that later on). You then present that view controller as you would any other modal or popover.

let string = "Hello, world!"
let url = URL(string: "https://nshipster.com")!
let image = UIImage(named: "mustache.jpg")
let pdf = Bundle.main.url(forResource: "Q4 Projections",
                            withExtension: "pdf")

let activityViewController =
    UIActivityViewController(activityItems: [string, url, image, pdf],
                             applicationActivities: nil)

present(activityViewController, animated: true) {
    // ...
}

When you run this code the following is presented on the screen:

UIActivityViewController

By default, UIActivityViewController shows all the activities available for the items provided, but you can exclude certain activity types via the excludedActivityTypes property.

activityViewController.excludedActivityTypes = [.postToFacebook]

Activity types are divided up into “action” and “share” types:

  • Action (UIActivityCategoryAction) activity items take an action on selected content, such as copying text to the pasteboard or printing an image.
  • Share (UIActivityCategoryShare) activity items share the selected content, such as composing a message containing a URL or posting an image to Twitter.

Each activity type supports certain kinds of items. For example, you can post a String, URL, and / or image to Twitter, but you can’t assign a string to be the photo for a contact.

The following tables show the available activity types for each category and their supported items:

UIActivityCategoryAction

String URL Image Files
airDrop
addToReadingList
assignToContact
copyToPasteboard
print
saveToCameraRoll

UIActivityCategoryShare

String URL Image Files
mail
message
postToFacebook
postToFlickr
postToTencentWeibo
postToTwitter
postToVimeo
postToWeibo

Creating a Custom UIActivity

In addition to the system-provided activities, you can create your own activities.

As an example, let’s create a custom activity that takes an image and applies a mustache to it via a web application.

Jony Ive Before Jony Ive After
Before After

Defining a Custom Activity Type

First, define a new activity type constant in an extension to UIActivity.ActivityType, initialized with a reverse-DNS identifier.

extension UIActivity.ActivityType {
    static let mustachify =
        UIActivity.ActivityType("com.nshipster.mustachify")
}

Creating a UIActivity Subclass

Next, create a subclass of UIActivity and override the default implementations of the activityCategory type property and activityType, activityTitle, and activityImage instance properties.

class MustachifyActivity: UIActivity {
    override class var activityCategory: UIActivity.Category {
        return .action
    }

    override var activityType: UIActivity.ActivityType? {
        return .mustachify
    }

    override var activityTitle: String? {
        return NSLocalizedString("Mustachify", comment: "activity title")
    }

    override var activityImage: UIImage? {
        return UIImage(named: "mustachify-icon")
    }

    // ...
}

Determining Which Items are Actionable

Activities are responsible for determining whether they can act on a given array of items by overriding the canPerform(withActivityItems:) method.

Our custom activity can work if any of the items is an image, which we identify with some fancy pattern matching on a for-in loop:

override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
    for case is UIImage in activityItems {
        return true
    }

    return false
}

Preparing for Action

Once an activity has determined that it can work with the specified items, it uses the prepare(withActivityItems:) to get ready to perform the activity.

In the case of our custom activity, we take the PNG representation of the first image in the array of items and stores that in an instance variable:

var sourceImageData: Data?

override func prepare(withActivityItems activityItems: [Any]) {
    for case let image as UIImage in activityItems {
        self.sourceImageData = image.pngData()
        return
    }
}

Performing the Activity

The perform() method is the most important part of your activity. Because processing can take some time, this is an asynchronous method. However, for lack of a completion handler, you signal that work is done by calling the activityDidFinish(_:) method.

Our custom activity delegates the mustachification process to a web app using a data task sent from the shared URLSession. If all goes well, the mustachioedImage property is set and activityDidFinish(_:) is called with true to indicate that the activity finished successfully. If an error occurred in the request or we can’t create an image from the provided data, we call activityDidFinish(_:) with false to indicate failure.

var mustachioedImage: UIImage?

override func perform() {
    let url = URL(string: "https://mustachify.app/")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = self.sourceImageData

    URLSession.shared.dataTask(with: request) { (data, _, error) in
        guard error == nil else {
            self.activityDidFinish(false)
            return
        }

        if let data = data,
            let image = UIImage(data: data)
        {
            self.mustachioedImage = image
            self.activityDidFinish(true)
        } else {
            self.activityDidFinish(false)
        }
    }
}

Showing the Results

The final step is to provide a view controller to be presented with the result of our activity.

The QuickLook framework provides a simple, built-in way to display images. We’ll extend our activity to adopt QLPreviewControllerDataSource and return an instance of QLPreviewController, with self set as the dataSource for our override of theactivityViewController method.

import QuickLook

extension MustachifyActivity: QLPreviewControllerDataSource {
    override var activityViewController: UIViewController? {
        guard let image = self.mustachioedImage else {
            return nil
        }

        let viewController = QLPreviewController()
        viewController.dataSource = self
        return viewController
    }

    // MARK: QLPreviewControllerDataSource

    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        return self.mustachioedImage != nil ? 1 : 0
    }

    func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
        return self.mustachioedImage!
    }
}

Providing a Custom Activity to Users

We can use our brand new mustache activity by passing it to the applicationActivities parameter in the UIActivityViewController initializer:

let activityViewController =
    UIActivityViewController(activityItems: [image],
                             applicationActivities: [Mustachify()])

present(activityViewController, animated: true) {
    // ...
}


There is a strong argument to be made that the long-term viability of iOS as a platform depends on sharing mechanisms like UIActivityViewController.

As the saying goes, “Information wants to be free.” Anything that stands in the way of federation is doomed to fail.

NSMutableHipster

Questions? Corrections? Issues and pull requests are always welcome.

This article uses Swift version 4.2 and was last reviewed on December 5, 2018. Find status information for all articles on the status page.

Written by Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon. He is the founder of NSHipster and Flight School, and the creator of several open source libraries, including AFNetworking and Alamofire.

Next Article

We knew that the Earth was not flat long before 1492. Early navigators observed the way ships would dip out of view over the horizon many centuries before the Age of Discovery. For many iOS developers, though, a flat MKMapView was a necessary conceit until recently.