11

I have an iOS app that is trying to read files from an external storage device without importing them into the App's sandbox.

I have followed Apple's documentations outlined here to do this --

Providing Access to Directories

I'm able to retrieve the selected directory ( which is on an external storage device connected via the Lightning port ) and enumerate the files inside the directory.

However, when I try to do something with those files as per the recommended pattern, I get a failure and basically get permission errors on the file.

        let shouldStopAccessing = pickedFolderURL.startAccessingSecurityScopedResource()
        defer {
          if shouldStopAccessing {
            pickedFolderURL.stopAccessingSecurityScopedResource()
          }
       }
       var coordinatedError:NSError?
       NSFileCoordinator().coordinate(readingItemAt: pickedFolderURL, error: &coordinatedError) { (folderURL) in
        let keys : [URLResourceKey] = [.isDirectoryKey]
        let fileList = FileManager.default.enumerator(at: pickedFolderURL, includingPropertiesForKeys: keys)!
        for case let file as URL in fileList {
            if !file.hasDirectoryPath {
                do {
                    // Start accessing a security-scoped resource.
                    guard file.startAccessingSecurityScopedResource() else {
                        // Handle the failure here.
                        //THIS ALWAYS FAILS!!
                        return
                    }

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

I should add that this works JUST FINE if the files are on iCloud Drive via Simulator. It fails both on external devices and iCloud Drive on a real device.

Here is a full working project that demonstrates the failure.

  1. Running on simulator accesses iCloud Drive files just fine. But running on device fails.
  2. Running on device to access USB drive fails.
Kal
  • 24,724
  • 7
  • 65
  • 65
  • I do see one major difference between your code and the example code Apple gives; you're saying `guard file.startAccessingSecurityScopedResource() else { return }`, but Apple says `else { continue }`. Could that make a difference? You're giving up if the _first_ file fails, but maybe there's something funny about that one file. – matt Mar 05 '20 at 01:22
  • @matt -- thanks for the comment. I have tried many different files to be certain. – Kal Mar 05 '20 at 01:25
  • I am curious, what is it exactly you want to do with the selected directory? From your code it looks like you want to make mutable writes to all the contents right? – Daniel Galasko Mar 06 '20 at 15:05
  • @DanielGalasko -- no . I just want to read the files there. the directory contains a bunch of mp3 files which I want to read and play. – Kal Mar 07 '20 at 19:19
  • Did you tried to copy your files to temp directory at first then read from it? – Nikita Mar 08 '20 at 11:18

1 Answers1

13

So, this seems like a documentation issue with the link posted above. When the user selects a folder, all files and folders are recursively granted access and automatically security scoped. The line guard file.startAccessingSecurityScopedResource() always returns false.

The trick to getting this work is NOT to try to security scope individual files, but to ensure that this code snippet does not run BEFORE you access files.

   defer {
      if shouldStopAccessing {
        pickedFolderURL.stopAccessingSecurityScopedResource()
      }
   }

As long as you continue to access files while the pickedFolderURL is inside security scope, you will be successful.

Hope this helps somebody.

Kal
  • 24,724
  • 7
  • 65
  • 65