Accessing Security Scoped Files

Your app cannot directly access files that are outside its own container. If you try to access external files, like those stored in iCloud, you’ll get a permissions error. To make those files accessible you need to work with security scopes.

Operation Not Permitted

I have a view that attempts to read text from a file to populate a text editor:

struct ContentView: View {
  @State private var text = ""
  @State private var isImporting = false
  @State private var error: Error?
  
  var body: some View {
    VStack {
      TextEditor(text: $text)
        .padding()
      
      if let error {
        ErrorView(error: error)
      }
    }
    .toolbar {
      ToolbarItem() {
        importButton
      }
    }
    .fileImporter(isPresented: $isImporting,
                  allowedContentTypes: [.text]) {
      let result = $0.flatMap { url in
        read(from: url)
      }
      switch result {
      case .success(let text):
        self.text += text
      case .failure(let error):
        self.error = error
      }
    }
  }
}

The file importer shows the system file picker and, when successful, gives us back a URL to a file. The read(from:) method returns the contents of the file as a String or an error if the operation fails:

private func read(from url: URL) -> Result<String,Error> {
  Result { try String(contentsOf: url) }
}

This works fine when I test it on the simulator but if I try it on a device and read a file from iCloud I get an error:

The file hello.txt couldn&rsquo;t be opened because you don&rsquo;t have permission to view it.

The full Foundation error message doesn’t tell us much more:

Error Domain=NSCocoaErrorDomain Code=257
"The file “hello.txt” couldn’t be opened because 
you don’t have permission to view it."
UserInfo={
 NSFilePath=/private/var/mobile/Library/Mobile Documents/
 com~apple~CloudDocs/Downloads/hello.txt,
 NSUnderlyingError=0x28091f960 {
  Error Domain=NSPOSIXErrorDomain Code=1 
  "Operation not permitted"
 }
}

I’m using SwiftUI in this example, but the result is the same if you’re using the UIKit document picker to get the url. What’s going on?

Security Scoped Resources

Apple explains the problem in the WWDC18 video Managing Documents In Your iOS Apps. When the document picker returns a url to a file that is outside our app’s container it comes with an associated security scoped permissions token.

We need to tell the system before and after accessing a security scoped url:

private func read(from url: URL) -> Result<String,Error> {
  let accessing = url.startAccessingSecurityScopedResource()
  defer {
    if accessing {
      url.stopAccessingSecurityScopedResource()
    }
  }
  return Result { try String(contentsOf: url) }
}

If the call to start accessing the url returns true our app can access the file. The defer block makes sure we balance the start and stop access methods even if our handling of the url throws an error.

You don’t need to check the url to decide whether to call these security scoped methods. They don’t do anything for files that are in your app’s container, so it’s safe to call them if in doubt.

Note: If you need to persist the url for state restoration it doesn’t save the security token. You’ll need to create and save a security scoped bookmark for that (see the WWDC video for details).

Learn More