Article

Supporting Suggestions in Your App’s Share Extension

Make your messaging app available for share sheet suggestions and use SiriKit intents to populate your app’s share extension.

Overview

Using the iOS share sheet, users can launch your messaging app instantly from a list of suggestions when sharing content like a link, image, video, or file. The share sheet suggests conversations with people in apps that the user interacts with frequently, and updates its suggestions over time based on the user’s favorite apps and conversations. Figure 1 shows suggestions that include a mix of apps and conversations with contacts.

Figure 1

A share sheet provides a list of suggestions

A screenshot of an iPhone. The upper half shows the apple.com website in Safari. The bottom half of the screen shows the share sheet after the user has decided to share the website with someone. iOS suggests conversations in the Messages app and another app.

To allow iOS to include conversations from your messaging app in the list of suggestions:

As the user selects an app from the list of suggestions, the app’s sharing interface, implemented as a share extension, accesses additional metadata. iOS provides the INSendMessageIntent for you to prepopulate the interface of your app’s share extension.

For example, you can access the conversationIdentifier property and preselect a conversation with which to share content so the user doesn’t need to search for a contact in a list, or type a friend’s name. Figure 2 shows an app’s sharing interface using the default system UI that’s based on an SLComposeViewController, with the recipient (Juan Chavez) already filled in.

Figure 2

Prepopulating the share extension's interface with a conversation

A screenshot of an iPhone with the Safari browser displaying the apple.com homepage. The user tapped the Share button and selected an app from the list of suggestions. The platforms default sharing interface, based on an SLComposeViewController, is visible. Based on the available metadata, the extension has prepopulated the interface with a recipient (Juan Chavez).

Add a Share Extension to Your App

To add a share extension to your app, open your app’s project in Xcode and select File > New > Target from the menu bar. Xcode presents a sheet that contains templates for different kinds of targets. Select the share extension template from the iOS pane and follow the steps in Xcode’s interface to add one to your app. To learn more, see Understand Share Extensions in the App Extension Programming Guide.

Support the Send Message Intent

Open the Share extension’s Info.plist, and expand the NSExtension and NSExtensionAttributes keys. Next, add a new entry with the IntentsSupported key and select Array for its value type. Add the string INSendMessageIntent as a new value to the array to declare support for the INSendMessageIntent intent type, as shown in Figure 3.

Figure 3

Add a new entry under the NSExtensionAttributes key

A screenshot of Xcode showing the Info.plist file for a Share extension that the developer created using Xcode's share extension template. The entries below the NSExtension key are expanded and show the IntentsSupported key with an item that has the value set to INSendMessageIntent. The NSExtensionActivationRule entry is set to TRUEPREDICATE.

To make debugging easier, the value of NSExtensionActivationRule is set to TRUEPREDICATE when you first add a share extension using Xcode’s template. Replace it with valid activation rules as described in Declaring Supported Data Types for a Share or Action Extension before submitting your app for review.

Donate a Send Message Intent

Donate an INSendMessageIntent when the user sends a message in your app and its share extension, and not in any other circumstances. For example, don’t donate an intent when the user hasn’t actually sent a message.

As you initialize the INSendMessageIntent object, provide metadata that will be available later when the user choses your app’s share extension from the list of suggestions. The following code donates an INSendMessageIntent with a groupName, a conversationIdentifier, and an INImage.

// Create an INSendMessageIntent to donate an intent for a conversation with Juan Chavez.
let groupName = INSpeakableString(spokenPhrase: "Juan Chavez")
let sendMessageIntent = INSendMessageIntent(recipients: nil,
                                            content: nil,
                                            speakableGroupName: groupName,
                                            conversationIdentifier: "sampleConversationIdentifier",
                                            serviceName: nil,
                                            sender: nil)

// Add the user's avatar to the intent.
let image = INImage(named: "Juan Chavez")
sendMessageIntent.setImage(image, forParameterNamed: \.speakableGroupName)

// Donate the intent.
let interaction = INInteraction(intent: sendMessageIntent, response: nil)
interaction.donate(completion: { error in
    if error != nil {
        // Add error handling here.
    } else {
        // Do something, e.g. send the content to a contact.
    }
})

When iOS includes a conversation within your app as a suggestion in the share sheet, it displays your app’s icon along with the INImage you associated with your INSendMessageIntent. If there’s no INImage set on the intent, iOS uses the image property from the INPerson object for each recipient. If the INPerson object’s image is nil, iOS looks up the corresponding contact in the Contacts app using the person’s contactIdentifier. The share sheet then uses the contact’s image.

Populate Your Share Extension’s Interface with Metadata

When the user selects your app from the list of suggestions, you can access the metadata that you created when your app donated the INSendMessageIntent. Use it to populate your share extension’s interface.

The following code listing shows a template implementation for an SLComposeServiceViewController subclass. It accesses the intent property, makes sure it’s an INSendMessageIntent, and uses the intent’s conversationIdentifier to create a new Recipient object. It then uses the Recipient object to populate the share extension’s interface in the configurationItems method.

import Intents
import Social
import UIKit

class ShareViewController: SLComposeServiceViewController {

    var recipient: Recipient = Recipient(withName: "Placeholder")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Populate the recipient property with the metadata in case the user tapped a suggestion from the share sheet.
        let intent = self.extensionContext?.intent as? INSendMessageIntent
        if intent != nil {
            let conversationIdentifier = intent!.conversationIdentifier
            self.recipient = recipient(identifier: conversationIdentifier!)
        }
    }

    func recipient(identifier: String) -> Recipient {
        // Create a recipient object, for example by loading it from a data base.
        return Recipient(withName: identifier)
    }

    override func isContentValid() -> Bool {
        // Do validation of contentText and/or NSExtensionContext attachments here
        return true
    }

    override func didSelectPost() {
        // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
        
        // Inform the host that we're done, so it un-blocks its UI.
        // Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
        self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

    override func configurationItems() -> [Any]! {
        // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
        
        // Use the Recipient object to populate the share sheet.
        let item = SLComposeSheetConfigurationItem()
        item?.title = NSLocalizedString("To:", comment: "The To: label when sharing content.")
        item?.value = self.recipient.name
        item?.tapHandler = {
            self.validateContent()
            item!.value = self.recipient.name
        }
        
        return [item!]
    }
}