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 Technical Business Analyst, IT Operations

Paweł is technical analyst, developer and IT operations specialist at Zaven. He can solve a 3x3x3 Rubik's cube 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.

Comments are closed.

Popular posts

From Hype to Hard Hats: Practical Use Cases for AI chatbots in Construction and Proptech.

From Hype to Hard Hats: Practical Use Cases for AI chatbots in Construction and Proptech.

Remember the multimedia craze in the early 2000s? It was everywhere, but did it truly revolutionize our lives? Probably not. Today, it feels like every piece of software is labeled "AI-powered." It's easy to dismiss AI chatbots in construction as just another tech fad.

Read more
Fears surrounding external support. How to address concerns about outsourcing software development?

Fears surrounding external support. How to address concerns about outsourcing software development?

Whether you’ve had bad experiences in the past or no experience at all, there will always be fears underlying your decision to outsource software development.

Read more
What do you actually seek from external support? Identify what’s preventing you from completing a project on time and within budget

What do you actually seek from external support? Identify what’s preventing you from completing a project on time and within budget

Let’s make it clear: if the capabilities are there, a project is best delivered internally. Sometimes, however, we are missing certain capabilities that are required to deliver said project in a realistic timeline. These may be related to skills (e.g. technical expertise, domain experience), budget (hiring locally is too expensive) or just capacity (not enough manpower). What are good reasons for outsourcing software development?

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