I have an NSDocument with some simple code:
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
self.string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return YES;
}
If I change the file in an external editor, how do I get notified of this so I can handle it? I assume there is something built in for this, but I can't find it.
I'm looking for something built into NSDocument. I'm aware of FSEvent, but that seems too low level to do something very common for most document-based apps.
Since OS X v10.7, NSDocument
provides a far simpler mechanism you can override in subclasses: -presentedItemDidChange
.
-presentedItemDidChange
, Ignoring Metadata ChangesJust relying on this callback can produce false positives, though, when metadata change. That got on my nerves quickly for files stored in Dropbox, for example.
My approach to deal with this in general, in Swift, is like this:
class MyDocument: NSDocument {
// ...
var canonicalModificationDate: Date!
override func presentedItemDidChange() {
guard fileContentsDidChange() else { return }
guard isDocumentEdited else {
DispatchQueue.main.async { self.reloadFromFile() }
return
}
DispatchQueue.main.async { self.showReloadDialog() }
}
fileprivate func showReloadDialog() {
// present alert "do you want to replace your stuff?"
}
/// - returns: `true` if the contents did change, not just the metadata.
fileprivate func fileContentsDidChange() -> Bool {
guard let fileModificationDate = fileModificationDateOnDisk()
else { return false }
return fileModificationDate > canonicalModificationDate
}
fileprivate func fileModificationDateOnDisk() -> Date? {
guard let fileURL = self.fileURL else { return nil }
let fileManager = FileManager.default
return fileManager.fileModificationDate(fileURL: fileURL)
}
}
Now you have to update the canonicalModificationDate
in your subclass, too:
-ignoreLatestFileChanges
so you don't nag your user ad infitium;-readFromURL:ofType:error:
or however you end up reading in contents for the initial value;-dataOfType:error:
or however you produce contents to write to disk.You want to register with the FSEvents API. Since 10.7, you can watch arbitrary files.
Potential duplicate of this question.
When I open a document in my document-based app, edit in in another application, and switch back to my app, the same method that you mentioned (readFromData:ofType:error:
) is called with the new data. This method is called when you restore a previous version from the Versions browser, too.
You could then add a boolean instance variable to check whether it's being called because of an external update (in my case, I check whether one of my IBOutlets is initialized: if it's not, the document is being loaded for the first time). You might want to move your code that makes use of the string
instance variable into some method that you can call if the document is already initialized, like this:
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
self.string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (self.isLoaded)
[self documentChanged];
return YES;
}
- (void)windowControllerDidLoadNib:(FCWindowController *)windowController {
self.isLoaded = YES;
[self documentChanged];
}
- (void)documentChanged {
// use self.string as you like
]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With