7

I want my users to be able to tap on a file in the Files app and have it sent to my app. Previously I've had the app setup to successfully be able to open files from Mail when files are shared that way using "Open in ". The filetype I'm using is custom to my app. The document type, Exported UTIs, and Imported UTIs were created in order to enable "Open in " and hasn't been changed.

I'm getting an error if the file hasn't been downloaded inside the Files app already (the small cloud with a down arrow is next to the filename). Even if you long tap on it and select "Share" and the use "Open in ", I get the same error.

If the file was added to Files from that device, it's already downloaded and tapping on it in Files works just fine. How can I force Files to download the file before sending it over to my app? I've searched online and the only reference I can find is here: https://forums.developer.apple.com/thread/47890 which the very last post talks about the same problem, but I can't find a solution anywhere.

In my info.plist file, I have my app setup to "Support opening documents in place" set to YES. I also have set "Application supports iTunes file sharing" set to YES.

I'm checking the value of options[.openInPlace] from the application(_:open:options:) method to detect that the file is coming from the Files app. I had to add the call to url.startAccessingSecurityScopedResource() before it would work at all when the file was already downloaded on the device.

Here's a stripped down version of the code that's doing the processing:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
let isOpenInPlace = options[.openInPlace] as? Bool
var securityScopedResource = false

if isOpenInPlace {
    securityScopedResource = url.startAccessingSecurityScopedResource()
}

do {
    let data = try Data(contentsOf: url)
    // Use the data to create something inside my app

    if securityScopedResource {
        url.stopAccessingSecurityScopedResource()
    }
} catch {
    print("ERROR: \(error.localizedDescription)")
    if securityScopedResource {
        url.stopAccessingSecurityScopedResource()
    }
}

The error I get is:

ERROR: The file <filename.ext> couldn't be opened because there is no such file

Is there any way to get this to work or is this an issue with Files?

(I'm using XCode 10.3, and swift 5, and testing on real devices: iphone 6 with iOS 11.4 and an iPadPro with iOS 12.1)

Edited on Aug 18 to add:

Further research and I've discovered I need to request that the file be downloaded from within my app. I've added the following to my code when I detect open in place = true, and the file doesn't already exist:

try FileManager.default.startDownloadingUbiquitousItem(at: url)

This method starts the download, but it's async and returns immediately. I'm trying to figure out a reliable way to kick off the download, and wait until it's completed before moving to the next step in my app. Any help would be appreciated!

N.W
  • 672
  • 2
  • 8
  • 19
  • How do you go from opening a file through Files then to receiving it through AppDelegate? – El Tomato Aug 12 '19 at 03:32
  • By declaring "Support opening documents in place" as YES, the OS sends them into your AppDelegate method `application(_:open:options:)`. The user just has to tap the file, and it gets sent to your app. That's method in AppDelegate is also where they are sent when the user selects "Open in " – N.W Aug 12 '19 at 03:59
  • Why don't you use `UIDocumentPickerController` so that they won't even get to pick an incomplete file? – El Tomato Aug 12 '19 at 04:11
  • Do you mean not downloaded by saying 'incomplete'? I don't want to have to implement a UIDocumentPickerController inside my app - I just want to be able to take files as input from iCloud. The files in iCloud (or on DropBox) will just be backup copies of their documents, I'm not actually editing them in place. – N.W Aug 12 '19 at 04:18
  • posting as comment rather than answer since i am *not* well versed in Swift, but https://stackoverflow.com/questions/42484281/waiting-until-the-task-finishes says you can utilize `DispatchGroup`'s `group.enter()`. `.leave()`, and `.wait()` to achieve this; but it also looks like you need to utilize `DispatchQueue` somehow, so i don't know whether that's accurate. – EarthToAccess Dec 13 '21 at 11:57

0 Answers0