iOS 14: NSFilePresenter not getting callbacks/notifications; works on iOS13

482 views Asked by At

I have an UIViewController that adopts the NSFilePresenter protocol. I have implemented presentedItemDidChange() in order to receive changes of files stored in an ubiquitous container on iCloud. My implementation is working OK on iOS13.7, but fails on iOS 14.

When I modify the presented files from an external process like "Finder" on macOS the presentedItemDidChange() is NOT called.

If this an a known bug in iOS 14 or am I missing something in my implementation ?

My implementation looks roughly like this:

var m_basename = "textfile.txt"

var iCloudURL: URL?  {
  get {
    guard let iCloudDocumentsBaseURL = FileManager.default.url(forUbiquityContainerIdentifier: iCloudContainerId) else {
      return nil
    }
    let iCloudDocumentsURL = iCloudDocumentsBaseURL.appendingPathComponent("Documents").appendingPathComponent(m_basename)
    return iCloudDocumentsURL
  }
}

override func viewDidLoad() {
  super.viewDidLoad()
  // Do any additional setup after loading the view.
  
  NSFileCoordinator.addFilePresenter(self)

  /* do some iCloud availability check here ; not shown */

  save(text: "Test string") { (success) in
    if success {
      print(#function, "Wrote file successfully")
    } else {
      print(#function, "Write file failed")
    }
  }
}

public func save(text: String, completion: @escaping (Bool) -> Void) {
  if let presenterurl = self.presentedItemURL {
    var errorMain: NSError?

    // set presenter if we load a file that is currently being presented
    var filePresenter: NSFilePresenter? = nil
    if presenterurl == iCloudURL {
      filePresenter = fPresenter
    }

    let coord = NSFileCoordinator(filePresenter: filePresenter)
    coord.coordinate(writingItemAt: activeURL, options: .forReplacing, error: &errorMain) { (writeUrl) in
      do {
        try text.write(toFile: writeUrl.path, atomically: true, encoding: String.Encoding.utf8)
        print(#function, "\(writeUrl) write OK")
        completion(true)
      } catch {
        print(#function, "\(writeUrl) write failed: \(error.localizedDescription)")
        completion(false)
      }
    }
    if errorMain != nil {
      print("Error: ", errorMain!.localizedDescription)
      completion(false)
    }
  }
  print(#function, "end of save() function")
}

// MARK: - NSFilePresenter
extension ViewController: NSFilePresenter {
  public var presentedItemURL: URL? {
      return iCloudURL
  }
  
  public var presentedItemOperationQueue: OperationQueue {
    return OperationQueue.main
  }
  
  public func presentedItemDidChange() {
    print(#function, "Loading file \(m_basename)")
  }
}

Thanks

1

There are 1 answers

0
malhal On

The docs state you can deadlock if you use the main queue:

/* The operation queue in which all of the other NSFilePresenter messages except -presentedItemURL will be sent to the receiver. Implementations of this method must be prepared to be invoked by Cocoa in any queue, at any time, including from within invocations of NSFileCoordinator methods. A nil value is not valid.

For example, NSDocument has a -presentedItemOperationQueue method that returns a private queue. In very simple cases you can return [NSOperationQueue mainQueue], but doing so is often an invitation to deadlocks.
*/
@property (readonly, retain) NSOperationQueue *presentedItemOperationQueue;