How do you provide a placeholder in NSFileProviderExtension?

I am having trouble getting my NSFileProviderExtension working with iOS 11 and the Files app. I have it enumerating fine and I implemented providePlaceholder exactly as prescribed in the documentation. The problem is the NSFileProviderManager.writePlaceholder always fails. I am trying to implement startProvidingItem(at url: completionHandler:) but seems I need to get past providing the placeholder first.


Here is the code and the accompanying debug output:

    override func providePlaceholder(at url: URL, completionHandler: @escaping (Error?) -> Void) {
        NSLog("providePlaceholder: url = %@", url.absoluteString)
        guard let identifier = persistentIdentifierForItem(at: url) else {
            completionHandler(NSFileProviderError(.noSuchItem))
            return
        }
      
        do {
            NSLog("persistentIdentifier = %@", identifier.rawValue)
            let fileProviderItem = try item(for: identifier)
            NSLog("fileProviderItem = %@", fileProviderItem.description)
            let placeholderURL = NSFileProviderManager.placeholderURL(for: url)
            NSLog("placeholderURL = %@", placeholderURL.absoluteString)
            try NSFileProviderManager.writePlaceholder(at: placeholderURL,
                                                       withMetadata: fileProviderItem)
          
            completionHandler(nil)
        }
        catch let error {
            NSLog("writePlaceholder error = %@", error.localizedDescription)
            completionHandler(error)
        }
    }


providePlaceholder: url = file:///private/var/mobile/Containers/Shared/AppGroup/90C5A58C-5F25-4CE8-8596-52933CD64B66/File%20Provider%20Storage/0/file%200.png


persistentIdentifier = 0


fileProviderItem = filename = file 0.png itemIdentifier = NSFileProviderItemIdentifier(_rawValue: 0) parentItemIdentifier = NSFileProviderItemIdentifier(_rawValue: NSFileProviderRootContainerItemIdentifier) capablities = NSFileProviderItemCapabilities(rawValue: 63) typeIdentifier = public.png


placeholderURL = file:///private/var/mobile/Containers/Shared/AppGroup/90C5A58C-5F25-4CE8-8596-52933CD64B66/File%20Provider%20Storage/0/.file%200.png.icloud


writePlaceholder error = The folder “.file 0.png.icloud” doesn’t exist.

I resolved this. Turns out a few lines were left out of the prescription for implementing this function. If you use the recommended url format of "File Provider Storage/<item identifier>/filename" you need to do a bit more work. In order for the placeholder to be created by NSFileProviderManager.writePlaceholder, the directory "File Provider Storage/<item identifier>" must already exist. This was not obvious to me and figured this out via trial and error. I figured using the file coordinator would be the right thing to do for creating the directory. I am wondering whether I need to use the file coordinator for writing the placeholder but since it wasn’t in the docs, I figured that NSFileProviderManager.writePlaceholder is already doing that. Would be great if somebody could chime in on that.


The modified code looks like this:


    override func providePlaceholder(at url: URL, completionHandler: @escaping (Error?) -> Void) {
        NSLog("providePlaceholder: url = %@", url.absoluteString)
        guard let identifier = persistentIdentifierForItem(at: url) else {
            completionHandler(NSFileProviderError(.noSuchItem))
            return
        }
     
        do {
            NSLog("persistentIdentifier = %@", identifier.rawValue)
            let fileProviderItem = try item(for: identifier)
            NSLog("fileProviderItem = %@", fileProviderItem.description)
            let placeholderURL = NSFileProviderManager.placeholderURL(for: url)
            let placecholderDirectoryUrl = placeholderURL.deletingLastPathComponent()
            var createDirectoryError:Error?
            if (!fileManager.fileExists(atPath: placecholderDirectoryUrl.absoluteString)) {
                var fcError: NSError?
                fileCoordinator!.coordinate(writingItemAt: placecholderDirectoryUrl, options: NSFileCoordinator.WritingOptions(rawValue: 0), error: &fcError
                    , byAccessor: { (newUrl) in
                        do {
                            createDirectoryError = fcError;
                            if (fcError == nil) {
                                try fileManager.createDirectory(at: newUrl, withIntermediateDirectories: true, attributes: nil)
                            }
                        } catch let fmError {
                            NSLog("createError = %@", fmError.localizedDescription)
                            createDirectoryError = fmError
                        }
                })
            }
         
            if let placeholderError = createDirectoryError {
                throw placeholderError
            }
            else {
                NSLog("placeholderURL = %@", placeholderURL.absoluteString)
                try NSFileProviderManager.writePlaceholder(at: placeholderURL,
                                                           withMetadata: fileProviderItem)
                completionHandler(nil)
            }
        }
        catch let error {
            NSLog("writePlaceholder error = %@", error.localizedDescription)
            completionHandler(error)
        }
    }

This turned out not to be the issue I was having, but I wanted to point out a slight bug in the solution code you posted:


if (!fileManager.fileExists(atPath: placecholderDirectoryUrl.absoluteString)) {


should be:


if (!fileManager.fileExists(atPath: placecholderDirectoryUrl.path)) {


otherwise it will always try to cretae the folder - not a huge problem, but may as well not do that work if not needed.

Hi tmrog36,


I get nothing in the line try NSFileProviderManager.writePlaceholder(at: placeholderURL, withMetadata: fileProviderItem).

It doesn't get catched too. it just keeps getting skipped. No idea what happening there.


Thanks

How do you provide a placeholder in NSFileProviderExtension?
 
 
Q