Article

Providing Access to Directories

Use a document picker to access the content of a directory outside your app’s container.

Overview

In iOS 12 and earlier, users could open and interact with files outside the app’s container. The UIDocumentBrowserViewController and UIDocumentPickerViewController provided access to files stored in the system’s local file provider, in iCloud, or in third-party services that use a File Provider extension. Users could select multiple files at a time—but they needed to select each file individually.

With iOS 13, users can select a directory from any of the available file providers using a UIDocumentPickerViewController. The document picker returns a security-scoped URL for the directory. Security-scoped URLs permit your app to access content outside its container. In this case, the URL lets your app recursively access the directory and all of its contents. This includes accessing any new items added to the directory in the future. Your app can even save a bookmark for this URL, letting it access the directory the next time it launches.

Ask the User to Select a Directory

To prompt the user to select a directory, create a document picker for an UIDocumentPickerModeOpen operation, setting the document type to kUTTypeFolder. Then set the document picker’s delegate and present it.

// Create a document picker for directories.
let documentPicker =
    UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String],
                                   in: .open)

documentPicker.delegate = self

// Set the initial directory.
documentPicker.directoryURL = startingDirectory

// Present the document picker.
present(documentPicker, animated: true, completion: nil)

As soon as you call the presentViewController:animated:completion: method, the system displays the document picker to the user. If you specify the directoryURL property, the document picker starts with the selected directory. Otherwise, it starts with the last directory chosen by the user.

A screenshot of the document picker, with the TextEdit directory selected.

After the user taps Done, the system calls your delegate’s documentPicker:didPickDocumentsAtURLs: method, passing an array of security-scoped URLs for the directories chosen by the user. Use a security-scoped URL to enumerate the content of the directory and any of its subdirectories, or to add, remove, or modify any files. For more information, see Access the Directory’s Content.

If the user taps Cancel, the system calls documentPickerWasCancelled: instead.

Access the Directory’s Content

When the user selects a directory in the document picker, your system gives your app permission to access that directory and all of its contents. The document picker returns a security-scoped URL for the directory. When you use one of these URLs to enumerate the directory’s content, the resulting URLs are also security-scoped. Finally, you can save a security-scoped URL as a bookmark, then later resolve it back into a security-scoped URL.

To access the content of a security-scoped URL, you must do the following:

  1. Before accessing the URL, call startAccessingSecurityScopedResource.

  2. Use a file coordinator to perform read or write operations on the URL’s contents.

  3. After you’re done accessing the URL, call stopAccessingSecurityScopedResource.

// Start accessing a security-scoped resource.
guard url.startAccessingSecurityScopedResource() else {
    // Handle the failure here.
    return
}

// Make sure you release the security-scoped resource when you are done.
defer { url.stopAccessingSecurityScopedResource() }

// Use file coordination for reading and writing any of the URL’s content.
var error: NSError? = nil
NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in
    
    let keys : [URLResourceKey] = [.nameKey, .isDirectoryKey]
    
    // Get an enumerator for the directory's content.
    guard let fileList =
        FileManager.default.enumerator(at: url, includingPropertiesForKeys: keys) else {
            output.append("*** Unable to access the contents of \(url.path) ***\n")
            return
    }
    
    for case let file as URL in fileList {
        // Also start accessing the content's security-scoped URL.
        guard url.startAccessingSecurityScopedResource() else {
            // Handle the failure here.
            continue
        }
                
        // Make sure you release the security-scoped resource when you are done.
        defer { url.stopAccessingSecurityScopedResource() }

        // Do something with the file here.
    }
}

Save the URL as a Bookmark

To access the URL in the future, save the URL as a NSURLBookmarkCreationMinimalBookmark, using its bookmarkDataWithOptions:includingResourceValuesForKeys:relativeToURL:error: method.

do {
    // Start accessing a security-scoped resource.
    guard url.startAccessingSecurityScopedResource() else {
        // Handle the failure here.
        return
    }
    
    // Make sure you release the security-scoped resource when you are done.
    defer { url.stopAccessingSecurityScopedResource() }
    
    let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
    
    try bookmarkData.write(to: getMyURLForBookmark())
}
catch let error {
    // Handle the error here.
}

You can then read the bookmark, and resolve it to a security-scoped URL again.

do {
    let bookmarkData = try Data(contentsOf: getMyURLForBookmark())
    var isStale = false
    let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale)
    
    guard !isStale else {
        // Handle stale data here.
        return
    }
    
    // Use the URL here.
}
catch let error {
    // Handle the error here.
}

Respond to Permission Changes

Users always have complete control over the apps that can access directories. After selecting a directory from the document picker, your app appears in Settings > Privacy > Files and Folders. This page lists all the apps that have permission to access shared directories, and users can revoke or restore permission for each app at any time.

This means your app must be ready to handle failures when accessing a directory’s content. Calls to the startAccessingSecurityScopedResource() method can fail, as well as any attempts to read or write to the URL. This is especially true when saving and resolving bookmarks to security-scoped URLs, because using saved bookmarks can greatly increase the amount of time users have to possibly change your app’s permissions.

See Also

Documents and Directories

Adding a Document Browser to Your App

Give users access to their local or remote documents from within your app.

Building a Document Browser-Based App

Use a document browser to provide access to the user’s text files.

Building an App Based on the Document Browser View Controller

Implement a custom document file format to manage user interactions with files on different cloud storage providers.

UIDocumentBrowserViewController

A view controller for browsing and performing actions on documents stored locally and in the cloud.

UIDocumentPickerViewController

A view controller that provides access to documents or destinations outside your app’s sandbox.

UIDocumentInteractionController

A view controller that previews, opens, or prints files whose file format cannot be handled directly by your app.