How to add extensions to iOS apps (part 4)

iOS Development, Tutorials

Welcome to the last part of our tutorial: how to add extensions to iOS apps. In this part, we will learn how to add data using a service from our extension.

In part three of “How to add extensions to iOS apps” we finished table implementation in the main app.

Adding files

Start by opening the ActionViewController of our extension. We can see quite a lot of code here.

By default, it is used for handling photos which are opened through the extension. We’re going to modify this template (or rather “rewrite it”). The first step is to delete the function logic in viewDidLoad().


import UIKit
import MobileCoreServices

class ActionViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
	
	@IBAction func done() {
		self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)
	}

}

We’ll get back to it in a minute, but let’s have a look at the Info.plist file in our extension. We must define what kind of files will it accept. Look for a setting called NSExtension: there you can find a dictionary called NSExtensionAttributes with the NSExtensionActivationRule key. Determine the file types there.

how to add data using a service from extension

Defining file types

If you’d like to find out more, you can find the docs for this topic here. Apple provides a few elementary file types which we can use. Unfortunately, there are only a couple of them. And sometimes it turns out that they are too common for an app’s specific requirements. In that case, it is useful to find out what the UTI is. We will use a table in which all required file types stored.

Assume that I want the extension to accept only.pdf, .txt and .doc extensions. We need more than basic settings to determine these types of files. To make it easier, we can use Predicate Format String. Right-click the fileInfo.plist and select Open As/Source Code.

Now we can see the content of an XML format file. Look for a key called NSExtensionActivationRule and delete the string TRUEPREDICATE. By default, this string enables all file types (but this also guarantees our app to be rejected from the App Store).

Instead of this string, paste following one:


<key>NSExtensionActivationRule</key>
<string>SUBQUERY (
  extensionItems,
  $extensionItem,
  SUBQUERY (
    $extensionItem.attachments,
    $attachment,
    ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.adobe.pdf" ||
    ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text" ||
    ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.microsoft.word.doc"
  ).@count == $extensionItem.attachments.@count
).@count == 1</string>

It allows us to use the following types of files:

  • com.adobe.pdf (PDF)
  • public.plain-text (TXT)
  • com.microsoft.word.doc (DOC)

The file content should look like this:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>CFBundleDevelopmentRegion</key>
   <string>en</string>
   <key>CFBundleDisplayName</key>
   <string>AppExtenstionSampleAction</string>
   <key>CFBundleExecutable</key>
   <string>$(EXECUTABLE_NAME)</string>
   <key>CFBundleIdentifier</key>
   <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
   <key>CFBundleInfoDictionaryVersion</key>
   <string>6.0</string>
   <key>CFBundleName</key>
   <string>$(PRODUCT_NAME)</string>
   <key>CFBundlePackageType</key>
   <string>XPC!</string>
   <key>CFBundleShortVersionString</key>
   <string>1.0</string>
   <key>CFBundleSignature</key>
   <string>????</string>
   <key>CFBundleVersion</key>
   <string>1</string>
   <key>NSExtension</key>
   <dict>
       <key>NSExtensionAttributes</key>
       <dict>
           <key>NSExtensionActivationRule</key>
          <string>SUBQUERY (
              extensionItems,
              $extensionItem,
              SUBQUERY (
              $extensionItem.attachments,
              $attachment,
              ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.adobe.pdf" ||
              ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text" ||
              ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.microsoft.word.doc"
              ).@count == $extensionItem.attachments.@count
              ).@count == 1</string>
       </dict>
       <key>NSExtensionMainStoryboard</key>
       <string>MainInterface</string>
       <key>NSExtensionPointIdentifier</key>
       <string>com.apple.ui-services</string>
   </dict>
</dict>
</plist>

Run the app and open a PDF file, then click the action button. The Action Menu that should appear will display a list of all the applications that can be used to open it.

iPhone app development

Action Menu

 

Our app won’t be visible because we must manually activate it first (that’s a standard behaviour for newly installed apps). Just tap “More” and mark your app as activated. Then, approve it.

adding data from extension to iOS apps

Action Menu with our app

Now the app is on our menu but nothing will happen if you choose it. That’s because we have deleted from it all the logic that is responsible for handling the opened files.

Adding file view

Open our extension’s storyboard titled MainInterface and delete Image View (it won’t be needed). On top of the view, add a label to display the file name to be saved and and constraints below the “Save” button.

Mobile enterprise solution

The extension’s storyboard

Set an outlet for the label and an action for the button. Leave the “Done” button just like it is (it closes the view without changing the file).

extension to iOS apps how to add data

Setting an outlet for the label and an action for the button

Saving the file

Now we will save our file. But first, enable the extension to use main target classes. Open the project settings, choose our target on the left side and click the Build Phases bookmark. In Compile Sources area click the ”+” button and choose the LocalStorageManager class.

iOS app development how to add data

Adding LocalStorageManager

This way we can choose the sources to be compiled while building our target. With that we don’t need to recreate a class to handle our data container and so we can use the already created one.

iOS app developer how to add data using a service

Adding Compile Sources

 

Open ActionViewController and add the code needed for handling the opened file. Then add the two following methods:

  • In the processPickedFile() method search for the file which we opened and then check if it belongs to one of the three extensions we whitelisted in the plist of our extension.
  • If everything goes as planned, we can send the opened file to setPickedFile(). To set the file name as the label, we need the lastPathComponent from the file’s NSURL

    func processPickedFile(){
        if(self.extensionContext!.inputItems.count > 0){
            let inputItem = self.extensionContext?.inputItems.first as! NSExtensionItem
            for provider: AnyObject in inputItem.attachments!{
                let itemProvider = provider as! NSItemProvider
                if(itemProvider.hasItemConformingToTypeIdentifier(kUTTypePDF as String) || itemProvider.hasItemConformingToTypeIdentifier(kUTTypePlainText as String) || itemProvider.hasItemConformingToTypeIdentifier("com.microsoft.word.doc")){
                    itemProvider.loadItemForTypeIdentifier(kUTTypePDF as String, options: nil, completionHandler: { (file, error) in
                        if(error == nil){
                            self.setPickedFile(file!)
                        }
                    })
                    break
                }
            }
        }
    }
    
    func setPickedFile(file: NSSecureCoding){
        if let fileURL = file as? NSURL{
            let fileName = fileURL.lastPathComponent
            self.fileNameLabel.text = fileName
        }
    }

Next, to the saveButtonTouchUpInside() method, which is connected with our “Save” button, add two lines:


LocalStorageManager.sharedInstance.insertData(self.fileNameLabel.text!)
self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)

Here we use the method created in our class to handle the container added as a binary to our extension. After adding the file, call the extension close() method.

At the end add the processPickedFile() method to the viewDidLoad() method. The code should look like this now:


import UIKit
import MobileCoreServices

class ActionViewController: UIViewController {
    
    @IBOutlet weak var fileNameLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.processPickedFile()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func processPickedFile(){
        if(self.extensionContext!.inputItems.count > 0){
            let inputItem = self.extensionContext?.inputItems.first as! NSExtensionItem
            for provider: AnyObject in inputItem.attachments!{
                let itemProvider = provider as! NSItemProvider
                if(itemProvider.hasItemConformingToTypeIdentifier(kUTTypePDF as String) || itemProvider.hasItemConformingToTypeIdentifier(kUTTypePlainText as String) || itemProvider.hasItemConformingToTypeIdentifier("com.microsoft.word.doc")){
                    itemProvider.loadItemForTypeIdentifier(kUTTypePDF as String, options: nil, completionHandler: { (file, error) in
                        if(error == nil){
                            self.setPickedFile(file!)
                        }
                    })
                    break
                }
            }
        }
    }
    
    func setPickedFile(file: NSSecureCoding){
        if let fileURL = file as? NSURL{
            let fileName = fileURL.lastPathComponent
            self.fileNameLabel.text = fileName
        }
    }
    
    @IBAction func saveButtonTouchUpInside(sender: AnyObject) {
        LocalStorageManager.sharedInstance.insertData(self.fileNameLabel.text!)
        self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)
    }
    
    @IBAction func done() {
        self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)
    }

}

If you run the app and choose a file (of course, it can be only one of the file types we decided to support), a view with the file name should appear. You should be able to save the file.

iOS apps

Running the app

Saving the file should close the view. If we open the app now, we will see the app’s name on the list.

Let’s make just one more change in our main target ViewController. At the end of the viewDidLoad() method, add the following line:


NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.viewWillEnterForeground(_:)), name:UIApplicationWillEnterForegroundNotification, object: nil)

Afterwards, add the method below that is suggested by the selector:


func viewWillEnterForeground(notification: NSNotification){
    self.tableView.reloadData()
}

Thanks to that, if we switch to sleep mode and then run the app again, we can be sure that the table will refresh every time.


Conclusion

As you can see, creating your own app extension isn’t that hard. But before doing that, your project must be properly prepared. Create a common container for the app, add classes as binaries to the extension, determine supported files types. And, of course, create the logic responsible for the file service itself.


You can have a look at the finished app in our GitHub repo.


 

Paweł Kuźniak Mobile Developer

Paweł is an iOS developer at Zaven and a committed Rubik's cube aficionado. He can solve a 3x3x3 in a matter of seconds and you can find more complicated ones lying around on his desk (solved, obviously). His problem-solving attitude is reflected in his work, as he likes to keep himself busy with non-trivial puzzles.

Popular posts

Offline Access in a Mobile App Used on Construction Sites

Offline Access in a Mobile App Used on Construction Sites

For the last few years, we have been working on a user-friendly construction project management app. This kind of tool includes an array of features, for example issuing tasks and document management. However, there is one crucial feature that is often overlooked but plays a vital role on construction sites: offline functionality.

Read more
Enhancing API Testing Efficiency with JavaScript in Postman

Enhancing API Testing Efficiency with JavaScript in Postman

API testing is essential for good software, and Postman has made it easier by letting us use JavaScript to check our work. In this article, we'll see how using JavaScript in Postman helps us make sure our software is working right, saving us time and making our testing process smoother.

Read more
Flutter application template with GitHub Actions

Flutter application template with GitHub Actions

This article explains how to create Flutter application template with GitHub Actions to save yourself time and nerves.

Read more
Mobile Apps

Get your mobile app in 3 easy steps!

1

Spec out

with the help of our
business analyst

2

Develop

design, implement
and test, repeat!

3

Publish

get your app out
to the stores


back to top