UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

UIActivityViewController by example

How to share content across the system

Paul Hudson       @twostraws

UIKit has one main class for sharing content from our apps, and it has a flexible design that lets other services connect to it. This class, UIActivityViewController, can handle sharing text, URLs, image, and more, and is also able to add our app’s own services alongside the others.

In this article I'll walk through some complete examples of using UIActivityViewController, partly so you can see what it’s capable of, and partly also so you have one reference guide to refer back to in the future.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Sharing basic content

Let’s start with the basics: UIActivityViewController is the easiest way to let users share content like text and URLs. For example, you share text like this:

let items = ["This app is my favorite"]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
present(ac, animated: true)

And you share URLs like this:

let items = [URL(string: "https://www.apple.com")!]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
present(ac, animated: true)

The end result varies depending on which share service the user selects. For example, If you select Twitter, both of those will work great, but if you select Facebook only the URL will be shared because Facebook doesn’t allow you to pre-enter text for users.

You can combine items together just by extending the array:

let items: [Any] = ["This app is my favorite", URL(string: "https://www.apple.com")!]

Sharing images

You can share images as well, and the code is identical to other media:

let items = [yourImage]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
present(ac, animated: true)

However, the default share sheet for images will offer users the chance to save the image to their photo library. That might seem fine, but writing to the photo library is a restricted operation. As a result, if users try to tap that options your app will crash.

To fix this problem we need to add a text string describing our intent. So, open your project’s Info.plist file, select any item, click +, then choose the key name “Privacy - Photo Library Additions Usage Description”. Give it the value “We need to write photos” then press return.

Adding a subject

If you start sharing some text and your users selects to share that using the Mail app, you’ll notice your text gets put into the body of the message with nothing in the subject.

To fix this you need to make an object that conforms to the UIActivityItemSource protocol. This might be whatever view controller triggered the sharing, but ideally it would be a separate object.

If you wanted to use your view controller, first make it conform to UIActivityItemSource, like this:

class ViewController: UIViewController, UIActivityItemSource {

Next, use self for your array of items, like this:

let items = [self]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
present(ac, animated: true)

Finally, make your object implement the two required methods from the protocol: a placeholder item for what you plan to share, and the item itself.

The first of these is used only so UIKit knows the type of data you want to share. If you’re just sharing something trivial then you can make this return the same data as whatever you’ll return from the second method, but if the data is big or complex then you can just return a dummy. What matters is that the first method must return the same type of data as the second – a string, for example.

Here’s an implementation of the two methods that returns a string:

func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
    return "The pig is in the poke"
}

func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
    return "The pig is in the poke"
}

At this point our share code actually does the same thing as just sharing the text directly – it’s no better.

However, we can now implement a third method from UIActivityItemSource that does accomplish what we want:

func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
    return "Secret message"
}

And now any user sharing our content using Mail will get both their title and body filled in.

Customizing content based on destination

Following the steps above to add a subject to shared data, we can also customize what gets shared based on the destination. For example, when sharing to Twitter you might want to use hashtags and a “via @yourUsername” string, but when using other platforms you might not.

This is all done using the same itemForActivityType method used above, but this time we’re going to look at the activityType parameter that gets passed in. UIKit has lots of these built in, such as .postToFacebook, .mail, .saveToCameraRoll, and .airDrop, but in this example we’re going to check for .postToTwitter so we can add hashtags and a “via” string:

func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
    if activityType == .postToTwitter {
        return "Download #MyAwesomeApp via @twostraws."
    } else {
        return "Download MyAwesomeApp from TwoStraws."
    }
}

Adding a custom action

Last but not least, I’d like to walk you through adding a custom application activity to show alongside the others. I’ve left this until last because it is by far the hardest, but if you want this behavior there isn’t an easier option.

First, you need an image that will be shown in the list of icons. This should be 60x60 points on iPhone, or 76x76 on iPad.

Second, you need to subclass UIActivity and complete quite a few properties and methods. Annoyingly, most of these properties are read-only, which means they must be implemented using override var and shadowed properties – temporary properties that hold our data until the real properties can be read.

So, press Cmd+N to create a new class, calling it “ExampleActivity” and making it subclass from UIActivity.

Now give this class the following four properties:

var _activityTitle: String
var _activityImage: UIImage?
var activityItems = [Any]()
var action: ([Any]) -> Void

The first will hold the title of the thing to share, the second will hold an image, the third whatever items are being shared, and the fourth is a closure that will be run when the user selects our item. The closure must accept an array of Any, which will be the contents of activityItems.

Note: In case you were wondering, _activityTitle and _activityImage both start with underscores because they shadow the real properties of UIActivity, activityTitle and activityImage. We can’t set those directly, so we set our own properties instead.

Next we need an initializer. This will let us create this activity item from elsewhere more easily, and its main job is to store the title and image we want to share, along with the action closure.

Add this to the class:

init(title: String, image: UIImage?, performAction: @escaping ([Any]) -> Void) {
    _activityTitle = title
    _activityImage = image
    action = performAction
    super.init()
}

There are four properties we need to override, starting with activityTitle and activityImage. These just need to return our shadowed properties:

override var activityTitle: String? {
    return _activityTitle
}

override var activityImage: UIImage? {
    return _activityImage
}

The other two properties to describe the activity to iOS: we give it a unique activity type so iOS can track which was selected, along with an activity category that says whether we are an action behavior (bottom row) or a share behavior (top row). Just like your app bundle, your activity type should in reverse domain name notation, such as com.hackingwithswift.project.shareURL.

Add these two properties next:

override var activityType: UIActivity.ActivityType {
    return UIActivity.ActivityType(rawValue: "com.yoursite.yourapp.activity")
}

override class var activityCategory: UIActivityCategory {
    return .action
}

Finally we need to override three methods. The first is called canPerform(), and should say whether your activity is able to handle the items it has been sent. This allows you to show your icon sometimes but not others, but here we’re going to say we support all types:

override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
    return true
}

The second method is called prepare(), and will be passed the sharing items our user selected. We’re just going to stash that away in the activityItems property:

override func prepare(withActivityItems activityItems: [Any]) {
    self.activityItems = activityItems
}

The final method is called when the user selects our icon. We don’t need anything fancy here, because we already have our action closure that we can call. However, we do need to call activityDidFinish(true) to let iOS know our action completed:

override func perform() {
    action(activityItems)
    activityDidFinish(true)
}

And that’s it! Now we can try adding that to an activity view controller:

let customItem = ExampleActivity(title: "Tap me!", image: UIImage(named: "YourImageName")) { sharedItems in
    guard let sharedStrings = sharedItems as? [String] else { return }

    for string in sharedStrings {
        print("Here's the string: \(string)")
    }
}

let items = ["Hello, custom activity!"]
let ac = UIActivityViewController(activityItems: items, applicationActivities: [customItem])
ac.excludedActivityTypes = [.postToFacebook]
present(ac, animated: true)
Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.4/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.