As with other extension types, the Action extension aims to extend elements of functionality from one app so that it is available for use within other apps. In the case of Action extensions, this functionality must generally fit the narrow definition of enabling the user to transform the content within a host app or view it differently. An app designed to translate text into different languages might, for example, provide an extension to allow the content of other apps to be similarly translated.
This chapter will introduce the concept of Action extensions in greater detail and put theory into practice by creating an example app and Action extension.
An Overview of Action Extensions
Action extensions appear within the action view controller, which is the panel that appears when the user taps the Share button within a running app. Figure 77-1, for example, shows the action view controller panel as it appears from within the Safari web browser running on an iPhone. Action extensions appear within the action area of this panel alongside the built-in actions such as printing and copying of content.
When an Action extension is created, it must declare the types of content with which it can work. The appearance or otherwise of the Action extension within the action view controller is entirely context-sensitive. In other words, an Action extension that can only work with text-based content will not appear as an option in the action view controller when the user is working with or viewing image or video content within a host app.
Unlike other extension types, there are two sides to an Action extension. In the first instance, there is the Action extension itself. An Action extension must be bundled with a containing app which must, in turn, provide some useful and meaningful functionality to the user. The other possibility is for host apps to be able to move beyond simply displaying the Action extension as an option within the action view controller. With the appropriate behavior implemented, a host app can receive modified content from an Action extension and constructively use it on the user’s behalf. Both concepts will be implemented in the remainder of this chapter and the next chapter by creating an example Action extension and host app.
About the Action Extension Example
The tutorial in the remainder of this and the next chapter is divided into two distinct phases. The initial phase involves the creation of an Action extension named “Change it Up” designed to display the text content of host apps in upper case and using a larger font so that it is easier to read. For the sake of brevity, the containing app will not provide any additional functionality beyond containing the extension. However, it is important to remember that it will need to do so in the real world.
The second phase of the tutorial involves the creation of a host app that can receive modified content back from the Action extension and use it to replace the original content. This will be covered in the next chapter entitled Receiving Data from an iOS 17 Action Extension.
Creating the Action Extension Project
An Action extension is created by adding an extension target to a containing app. Begin by launching Xcode and creating a new project using the iOS App template with the Swift and Storyboard options selected, entering ActionDemo as the product name.
Adding the Action Extension Target
With the newly created project loaded into Xcode, select the File -> New -> Target… menu option and, in the template panel (Figure 77-2), select the options to create an iOS Application Extension using the Action Extension template:
With the appropriate options selected, click the Next button and enter MyActionExt into the Product Name field. Leave the remaining fields to default values and click on Finish to complete the extension creation process. When prompted, click on the Activate button to activate the scheme created by Xcode to enable the extension to be built and run.
Once the extension has been added, it will appear in the project navigator panel under the MyActionExt folder.
This folder will contain the Swift source code file for the extension’s view controller named ActionViewController.swift, a user interface storyboard file named MainInterface.storyboard, and an Info.plist file.
Changing the Extension Display Name
An important configuration change concerns the name that will appear beneath the extension icon in the action view controller. This is dictated by the value assigned to the Bundle display name key within the Info.plist file for the extension and currently reads “MyActionExt”. To change this, select the ActionDemo entry at the top of the Project Navigator panel, followed by MyActionExt listed under Targets. Finally, change the Display Name setting to “Change it Up” as illustrated below:
Designing the Action Extension User Interface
The user interface for the Action extension is contained in the MyActionExt -> MainInterface.storyboard file. Locate this file in the project navigator panel and load it into Interface Builder.
By default, Xcode has created a template user interface consisting of a toolbar, a “Done” button, and an image view. Therefore, the only change necessary for the purposes of this example is to replace the image view with a text view. First, select the image view in the storyboard canvas and remove it using the keyboard Delete key. Next, from the Library panel, drag and drop a Text View object onto the storyboard and position and resize it to fill the space previously occupied by the image view.
With the new Text View object still selected, display the Resolve Auto Layout Issues menu and select the Reset to Suggested Constraints menu option.
Display the Attributes Inspector for the Text View object, delete the default Latin text, and turn off the Editable option in the Behavior section of the panel. As previously outlined, one of the features of this extension is that the content is displayed in a larger font, so take this opportunity to increase the font setting in the Attribute Inspector from System 14.0 to System 39.0.
Finally, display the Assistant Editor panel and establish an outlet connection for the Text View object named myTextView. Then, remaining within the Assistant Editor, delete the line of code declaring the imageView outlet.
With these changes made, the user interface for the Action extension view controller should resemble that of Figure 77-4:
Receiving the Content
The tutorial’s next step is adding code to receive the content from a host app when the extension is launched. All extension view controllers have an associated extension context in the form of an instance of the NSExtensionContext class. A reference to the extension context can be accessed via the extensionContext property of the view controller.
The extension context includes a property named inputItems in the form of an array containing objects which provide access to the content from the host app. The input items are, in turn, contained within one or more NSExtensionItem objects.
Within the ActionViewController.swift file, locate the viewDidLoad method, remove the template code added by Xcode, and modify the method to obtain a reference to the first input item object:
override func viewDidLoad() {
super.viewDidLoad()
let textItem = self.extensionContext!.inputItems[0]
as! NSExtensionItem
}
Code language: Swift (swift)
Each NSExtensionItem object contains an array of attachment objects. These attachment objects are of type NSItemProvider and provide access to the data held by the host app. Once a reference to an attachment has been obtained, the hasItemConformingToTypeIdentifier method of the object can be called to verify that the host app has data of the type supported by the extension. In the case of this example, the extension supports text-based content, so the UTType.text uniform type identifier (UTI) is used to perform this test:
override func viewDidLoad() {
super.viewDidLoad()
let textItem = self.extensionContext!.inputItems[0]
as! NSExtensionItem
let textItemProvider = textItem.attachments![0]
if textItemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
}
}
Code language: Swift (swift)
Assuming that the host app has data of the required type, it can be loaded into the extension via a call to the loadItem(forTypeIdentifier:) method of the attachment provider object, once again passing through as an argument the UTI content type supported by the extension. The loading of the data from the host app is performed asynchronously, so a completion handler must be specified, which will be called when the data loading process is complete:
override func viewDidLoad() {
super.viewDidLoad()
let textItem = self.extensionContext!.inputItems[0]
as! NSExtensionItem
let textItemProvider = textItem.attachments![0]
if textItemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
textItemProvider.loadItem(
forTypeIdentifier: UTType.text.identifier,
options: nil,
completionHandler: { (result, error) in
})
}
}
Code language: Swift (swift)
When the above code is executed, the data associated with the attachment will be loaded from the host app, and the specified completion handler (in this case, a closure) will be called. The next step is to implement this completion handler. Remaining within the ActionViewController.swift file, declare a variable named convertString and implement the handler code in the closure so that it reads as follows:
import UIKit
import MobileCoreServices
class ActionViewController: UIViewController {
@IBOutlet weak var myTextView: UITextView!
var convertedString: String?
.
.
.
if textItemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
textItemProvider.loadItem(forTypeIdentifier: UTType.text.identifier,
options: nil,
completionHandler: { (result, error) in
self.convertedString = result as? String
if self.convertedString != nil {
self.convertedString = self.convertedString!.uppercased()
DispatchQueue.main.async {
self.myTextView.text = self.convertedString!
}
}
})
}
.
.
Code language: Swift (swift)
The first parameter to the handler closure is an object that conforms to the NSSecureCoding protocol (in this case, a string object containing the text loaded from the host app). This string is assigned to a new variable within the method’s body before being converted to upper case.
The converted text is then displayed on the Text View object in the user interface. It is important to be aware that because this is a completion handler, the code is being executed in a different thread from the main app thread. As such, any changes made to the user interface must be dispatched to the main thread, hence the DispatchQueue method wrapper.
Returning the Modified Data to the Host App
The final task of implementing the Action extension is to return the modified content to the host app when the user taps the Done button in the extension user interface. When the Action extension template was created, Xcode connected the Done button to an action method named done. Locate this method in the ActionViewController. swift file and modify it so that it reads as follows:
@IBAction func done() {
let returnProvider =
NSItemProvider(item: convertedString as NSSecureCoding?,
typeIdentifier: UTType.text.identifier)
let returnItem = NSExtensionItem()
returnItem.attachments = [returnProvider]
self.extensionContext!.completeRequest(
returningItems: [returnItem], completionHandler: nil)
}
Code language: Swift (swift)
This method essentially reverses the process of unpacking the input items. First, a new NSItemProvider instance is created and configured with the modified content (represented by the string value assigned to the convertedString variable) and the content type identifier. Next, a new NSExtensionItem instance is created, and the NSItemProvider object is assigned as an attachment.
Finally, the completeRequest(returningItems:) method of the extension context is called, passing through the NSExtensionItem instance as an argument.
As will be outlined later in this chapter, whether the host app does anything with the returned content items depends on whether the host app has been implemented to do so.
Testing the Extension
To test the extension, begin by making sure that the MyExtAction scheme (and not the ActionDemo containing app) is selected in the Xcode toolbar as highlighted in Figure 77-5:
Build and run the extension with a suitable device connected to the development system. As with other extension types, Xcode will prompt for a host app to work with the extension. An app that works with text content must be selected for the extension to be activated. Scroll down the list of apps installed on the device to locate and select the standard Notes app (Figure 77-6). Once selected, click on the Run button:
After the project has been compiled and uploaded to the device, the Notes app should automatically load (manually launch it if it does not). Select an existing note within the app, or add a new one if none exists, then tap the Share button in the top toolbar. If the Action extension does not appear in the action view controller panel, tap the Edit Actions… button and turn on the extension using the switch in the Activities list:
Once the extension has been enabled, it should appear in the action view controller panel:
Once the extension is accessible, select it to launch the action. At this point, the extension user interface should appear, displaying the text from the note in uppercase using a larger font:
Note that there may be a delay of several seconds between selecting the Change it Up extension and the extension appearing. Having verified that the Action extension works, tap the Done button to return to the Notes app. Note that the content of the note did not change to reflect the content change to uppercase that was returned from the extension. This is because the Notes app has not implemented the functionality to accept modified content from an Action extension. As time goes by and Action extensions become more prevalent, it will become more common for apps to accept modified content from Action extensions. This raises the question of how this is implemented, an area that will be covered in the next chapter entitled Receiving Data from an iOS 17 Action Extension.
Summary
An Action extension is narrowly defined as a mechanism to transform the content in a host app or display that content to the user differently. An Action extension is context-sensitive and must declare the type of data it can use. There are essentially three key elements to an Action extension. First, the extension must load the content data from the host app. Second, the extension displays or transforms that content in some app-specific way. Finally, the transformed data is packaged and returned to the host app. Not all host apps can handle data returned from an Action extension, and in the next chapter, we will explore the steps necessary to add such a capability.