iOS Extensions: Document Provider Tutorial

In this Document Provider tutorial, you’ll learn how to create a UIDocumentProvider extension that allows other apps to interact with your app’s documents. By Dave Krawczyk.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Setting Up App Groups

Xcode is very nice and has already started setting up app groups for you.

document provider tutorial

Navigate to your project settings Capabilities pane and select the PickerFileProvider target. The App Groups capability should have an identifier selected. Fix the issues for that target, then do the same for the Picker target. Finally, in the CleverNote target, enable app groups and select the identifier that was selected for the other two targets.

Note: In order to change the App Group information, you must have admin access for the Apple Developer Account. If you do not have this type of access, you will receive a cryptic message like Apple Connection Failed. You must have permissions to change App Group settings for this to work correctly.

Next, make sure the CleverNote build target is selected, then build and run on your device. Using the Pages app, tap the Locations button on the left of the navigation bar. This will bring up a list of apps that have Document Provider extensions.

pages

Note: If you receive an error when trying to load the extension, you might have to turn the device you’re using off and on again. This is due to an Apple bug that causes registration to fail on devices such as the iPhone 6s

Next, select the row titled More and switch on the Picker extension from the list of available providers. Tap Done to exit the menu, then select your extension. If you see a screen similar to the one below, you’re on the right track.

document provider tutorial

This is the default interface for a Document Picker. You’ll set up your own custom interface in a few minutes, but for now just copy down your app group identifier, because you’re about to start coding—yay!

Storing to the Shared Container

Now that your app has a document provider extension and your app group is set up, you need to make sure the extension can see your app’s files. To do this, you need to write files to the app group’s shared container instead of the application container (the Documents Directory).

Open Note.swift, then add the following line below the fileExtension constant, replacing YOUR APP GROUP IDENTIFIER with your own app group identifier:

static let appGroupIdentifier = "YOUR APP GROUP IDENTIFIER"

Next, add the following method above the localDocumentsDirectoryURL() method:

static func appGroupContainerURL() -> URL? {
  // 1
  let fileManager = FileManager.default
  guard let groupURL = fileManager
    .containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
      return nil
  }
    
  let storagePathUrl = groupURL.appendingPathComponent("File Provider Storage")
  let storagePath = storagePathUrl.path
  // 2
  if !fileManager.fileExists(atPath: storagePath) {
    do {
      try fileManager.createDirectory(atPath: storagePath,
                                              withIntermediateDirectories: false,
                                              attributes: nil)
    } catch let error {
      print("error creating filepath: \(error)")
      return nil
    }
  }
  // 3
  return storagePathUrl
}

Let’s look at this step by step. With this code, you:

  1. Use NSFileManager‘s containerURL(forSecurityApplicationGroupIdentifier:) to get the URL for the app group’s shared storage container, then append the ‘File Provider Storage’ path component to the location.
  2. Create the directory, if it does not already exist on the file system.
  3. Return the URL to your app group container’s File Provider Storage directory.

Next, replace the calls to localDocumentsDirectoryURL() with appGroupContainerURL() inside the fileUrlForDocumentNamed(_:) and getAllNotesInFileSystem() methods. The app will now create files in the app group’s shared storage container instead of inside the applications container.

Build and run. Since you’ve changed where the app is pulling files from, you won’t see any of the previously created notes. Go ahead and create a note.

You’ll notice that the note’s text isn’t being loaded when you try to open a saved note. This will be resolved once you hook up the File Provider, but first you’ll want to create the Document Picker experience.

Creating the Document Picker Experience

It’s up to you to create the user interface for the DocumentPickerViewController. There are two main categories of operation you need to plan for when thinking about UIDocumentPickerModes:

  • Choosing a file. Both the UIDocumentModeImport and UIDocumentModeOpen tasks require the user to choose a file the app has made available for the task.
  • Provide a file. Both the UIDocumentModeExportToService and UIDocumentModeMoveToService tasks allow other apps to provide a file to your app. For this feature, the user would wish to see either a confirmation button or, if your app has a hierarchical file system, a file directory listing to choose the specific location.

Let’s start with the first category.

Import/Open Experience

Under the Picker directory, open the MainInterface.storyboard and delete the default ‘Untitled.txt’ button on the DocumentPickerViewController scene. Add a UITableView and connect it to the DocumentPickerViewController class as an IBOutlet named tableView.

Next, add a prototype cell and give it the reuse identifier noteCell. Open DocumentPickerViewController.swift and add the following to the top of your class definition:

var notes = [Note]()

When the DocumentPickerViewController loads, the notes array will be populated with notes from the app’s shared container. Conveniently, there is a static method on the Note class that provides this information. To call this method, include the Picker target inside the Note.swift file’s target membership, as shown below:

document provider tutorial

Open DocumentPickerViewController.swift and add the following method below your IBOutlet definitions:

override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)

  notes = Note.getAllNotesInFileSystem()

  tableView.reloadData()
}

The code above populates the notes array and tells the UITableView to reload itself.

Remove the openDocument(_:) method provided by Xcode, since you do not require this method, then add the following UITableViewDataSource extension at the end of the DocumentPickerViewController file:

// MARK: - UITableViewDataSource
extension DocumentPickerViewController: UITableViewDataSource {
  // MARK: - Cell Identifiers
  private enum CellIdentifier: String {
    case noteCell = "noteCell"
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return notes.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.noteCell.rawValue,
                                             for: indexPath)
    
    let note = notes[indexPath.row]
    cell.textLabel?.text = note.title
    return cell
  }
}

This extension provides a private enumeration holding the tableView‘s cell identifiers and implements the required methods from the UITableViewDataSource protocol.

Finally, add the following UITableViewDelegate extension below the UITableViewDataSource extension:

// MARK: - UITableViewDelegate
extension DocumentPickerViewController: UITableViewDelegate {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    let note = notes[indexPath.row]
    dismissGrantingAccess(to: note.fileURL)
  }
}

This extension handles file selection, providing the URL of the note to the dismissGrantingAccessToURL(_:) method that sends the note URL back to the third-party calling app that initiated the Document Picker.

Build and run the app; you shouldn’t see any differences. Open the Pages app again and repeat the process to open the Picker extension for your app. You should now see a list of all files currently in the app’s shared container, in alphabetical order.

document provider tutorial

Select a file from the Picker; you’ll notice that it doesn’t load. Don’t worry—you’ll resolve this once you finally hook up the elusive File Provider!

Dave Krawczyk

Contributors

Dave Krawczyk

Author

Over 300 content creators. Join our team.