I am trying to work out how to sync a .plist file I have in the "Application Support" folder in my Sandboxed app for the Mac. I know I could use the iCloud key value store, but there is a limit of 64KB per app, which may or may not be hit depending on how many thing the user adds to the app!
I have read as much of the Apple documentation as possible, but I am still rather confused :(
Has anyone does something similar to this?
Thanks
You should create a subclass of UIDocument and use it with ubiquity directories.
There are 2 methods responsible for handling read/write. This one is called when reading:
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
And this one when writing:
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
All open/save actions are called automatically, you don't have to do anything. Howewer, there are methods that force open/save. Call this when opening:
- (void)openWithCompletionHandler:(void (^)(BOOL success))completionHandler
/* --- EXAMPLE --- */
MyDocument *doc = [[MyDocument alloc] initWithFileURL:ubiquitousFileURL];
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
// do sth
} else {
// handle error
}
}];
... and this when saving:
- (void)saveToURL:(NSURL *)url forSaveOperation:(UIDocumentSaveOperation)saveOperation completionHandler:(void (^)(BOOL success))completionHandler
/* --- EXAMPLE --- */
MyDocument *doc = [[MyDocument alloc] initWithFileURL:ubiquitousPackage];
[doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success) {
// do sth
} else {
// handle error
}
}];
There are many tutorials on the web, here are some examples that I used for learning:
Beginning iCloud in iOS 5 Tutorial Part 1
Beginning iCloud in iOS 5 Tutorial Part 2
UIDocument Class Reference may also help.
Related
When opening a locked file using my NSPersistentDocument subclass I get the following message in the console:
Attempt to add read-only file at path [URL] read/write. Adding
it read-only instead. This will be a hard error in the future; you
must specify the NSReadOnlyPersistentStoreOption.
The document window title is '(document name) - Locked'. After the user unlocks it, makes a change and then attempts to save, the save fails with the error
An error occurred while saving.
It seems that NSPersistentDocument fails to recognize that the user has unlocked the document and doesn't reopen it in read/write mode. Is this a bug in NSPersistentDocument or am I missing something here?
I am not overriding any of the file I/O methods in NSPersistentDocument.
Ah, ok automatic file locking.
That happens for auto-save documents not accessed in a while.
The typical approach is to notice the lock before creating the core data stack and put up a dialog asking the user to unlock the file.
If they agree to unlock the file, you simply unlock it and run as normal.
If they don't agree to unlock it, you copy it or open it readonly. Of course, you could simply bypass the user's preference and automatically unlock the file anyway, but that's probably not very nice.
Here is a category that should help you determine if a file is locked, and also lock/unlock the file.
Note, that this is entirely separate from the files mode being changed to read-only, but you can handle it in a similar manner.
Category interface
#interface NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path;
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error;
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error;
#end
Category implementation
#implementation NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path {
return [[[self attributesOfItemAtPath:path error:NULL]
objectForKey:NSFileImmutable] boolValue];
}
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error {
return [self setAttributes:#{NSFileImmutable:#NO}
ofItemAtPath:path
error:error];
}
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error {
return [self setAttributes:#{NSFileImmutable:#YES}
ofItemAtPath:path
error:error];
}
#end
Then, you can call [[NSFileManager defaultManager] isFileLockedAtPath:path] to determine if it is locked, and if it is, throw up a dialog asking the user what to do about it. You can then unlock it and open the stack as normal, or leave it locked and open the stack read-only, which will prevent saves from changing the file store.
Note that you can also monitor the file, and know when it changes from locked/unlocked and respond accordingly.
For Apple's guidelines on this, see https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/StandardBehaviors/StandardBehaviors.html
EDIT
Ok. I would have liked for NSPersistentDocument to replicate the
behavior in NSDocument - where the prompt to unlock comes only when an
edit is attempted. What you're saying is that there is no such feature
in NSPersistentDocument? – Aderstedt
OK. I thought you were wanting to ask the user to unlock it so that it could be opened read/write.
If you want to "go with the flow" and open it read-only when necessary, then you should add a little customization to your NSPersistentDocument subclass.
First, you want to add a little state to keep track of whether or not the original options specified a read-only file.
#implementation MyDocument {
BOOL explicitReadOnly;
}
Then, you will want a couple of utility methods...
- (NSDictionary*)addReadOnlyOption:(NSDictionary*)options {
NSMutableDictionary *mutable = options ? [options mutableCopy]
: [NSMutableDictionary dictionary];
mutable[NSReadOnlyPersistentStoreOption] = #YES;
return [mutable copy];
}
- (NSDictionary*)removeReadOnlyOption:(NSDictionary*)options {
NSMutableDictionary *mutable = options ? [options mutableCopy]
: [NSMutableDictionary dictionary];
[mutable removeObjectForKey:NSReadOnlyPersistentStoreOption];
return [mutable copy];
}
Next, you want to provide your own persistent store coordinator configuration code. This allows you to provide the read-only option to the store when you create it. This method is automatically called when you build your document, all you need to do is provide an override implementation.
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url
ofType:(NSString *)fileType
modelConfiguration:(NSString *)configuration
storeOptions:(NSDictionary<NSString *,id> *)storeOptions
error:(NSError * _Nullable __autoreleasing *)error {
explicitReadOnly = [storeOptions[NSReadOnlyPersistentStoreOption] boolValue];
if (![[NSFileManager defaultManager] isWritableFileAtPath:url.path]) {
storeOptions = [self addReadOnlyOption:storeOptions];
}
return [super configurePersistentStoreCoordinatorForURL:url
ofType:fileType
modelConfiguration:configuration
storeOptions:storeOptions
error:error];
}
Also, notice that NSPersistentDocument implements the NSFilePresenter protocol. Thus, you can override a method and be notified whenever the file content or attributes are changed. This will notify you for any change to the file, including lock/unlock from within your application, the Finder, or any other mechanism.
- (void)presentedItemDidChange {
[self ensureReadOnlyConsistency];
[super presentedItemDidChange];
}
We then want to ensure that our persistent store remains consistent with the read-only properties of the file.
Here is one implementation, that just changes the store's readOnly property.
- (void)ensureReadOnlyConsistency {
NSURL *url = [self presentedItemURL];
BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
[psc performBlock:^{
NSPersistentStore *store = [psc persistentStoreForURL:url];
if (store) {
if (fileIsReadOnly) {
if (!store.isReadOnly) {
store.readOnly = YES;
}
} else if (!explicitReadOnly) {
if (store.isReadOnly) {
store.readOnly = NO;
}
}
}
}];
}
This works, but has one little hangup. If the store is originally opened with read-only options, then the very first time the readOnly attribute is set to NO, that first save throws (actually, it's the obtainPermanentIDsForObjects:error: call. Core data appears to catch the exception, but it is logged to the console.
The save continues, and nothing seems amiss. All the objects get saved, and the object IDs are properly obtained and recorded as well.
So, there is nothing that does not work that I can tell.
However, there is another more draconian option, but it avoids the aforementioned "issue." You can replace the store.
- (void)ensureReadOnlyConsistency {
NSURL *url = [self presentedItemURL];
BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
[psc performBlock:^{
NSPersistentStore *store = [psc persistentStoreForURL:url];
if (store) {
if (fileIsReadOnly != store.isReadOnly) {
NSString *type = store.type;
NSString *configuration = store.configurationName;
NSDictionary *options = store.options;
if (fileIsReadOnly) {
options = [self addReadOnlyOption:options];
} else if (!explicitReadOnly) {
options = [self removeReadOnlyOption:options];
}
NSError *error;
if (![psc removePersistentStore:store error:&error] ||
![psc addPersistentStoreWithType:type
configuration:configuration
URL:url
options:options
error:&error]) {
// Handle the error
}
}
}
}];
}
Finally, note that the notification happens when the operating system notices that the file has changed. When the file is locked/unlocked from within your application, you can get a faster notification.
You can override these two methods to get a little quicker response to the change...
- (void)lockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
[super lockWithCompletionHandler:^(NSError * _Nullable error) {
if (completionHandler) completionHandler(error);
if (!error) [self ensureReadOnlyConsistency];
}];
}
- (void)unlockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
[super unlockWithCompletionHandler:^(NSError * _Nullable error) {
if (completionHandler) completionHandler(error);
if (!error) [self ensureReadOnlyConsistency];
}];
}
I hope that's what you are looking for.
I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)
I'm trying to create a Core Data, document based app but with the limitation that only one document can be viewed at a time (it's an audio app and wouldn't make sense for a lot of docs to be making noise at once).
My plan was to subclass NSDocumentController in a way that doesn't require linking it up to any of the menu's actions. This has been going reasonably but I've run into a problem that's making me question my approach a little.
The below code works for the most part except if a user does the following:
- Tries to open a doc with an existing 'dirty' doc open
- Clicks cancel on the save/dont save/cancel alert (this works ok)
- Then tries to open a doc again. For some reason now the openDocumentWithContentsOfURL method never gets called again, even though the open dialog appears.
Can anyone help me work out why? Or perhaps point me to an example of how to do this right? It feels like something that must have been implemented by a few people but I've not been able to find a 10.7+ example.
- (BOOL)presentError:(NSError *)error
{
if([error.domain isEqualToString:DOCS_ERROR_DOMAIN] && error.code == MULTIPLE_DOCS_ERROR_CODE)
return NO;
else
return [super presentError:error];
}
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError
{
if(self.currentDocument) {
[self closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(openUntitledDocumentAndDisplayIfClosedAll: didCloseAll: contextInfo:)
contextInfo:nil];
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:#"Suppressed multiple documents" forKey:NSLocalizedDescriptionKey];
*outError = [NSError errorWithDomain:DOCS_ERROR_DOMAIN code:MULTIPLE_DOCS_ERROR_CODE userInfo:details];
return nil;
}
return [super openUntitledDocumentAndDisplay:displayDocument error:outError];
}
- (void)openUntitledDocumentAndDisplayIfClosedAll:(NSDocumentController *)docController
didCloseAll: (BOOL)didCloseAll
contextInfo:(void *)contextInfo
{
if(self.currentDocument == nil)
[super openUntitledDocumentAndDisplay:YES error:nil];
}
- (void)openDocumentWithContentsOfURL:(NSURL *)url
display:(BOOL)displayDocument
completionHandler:(void (^)(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error))completionHandler NS_AVAILABLE_MAC(10_7)
{
NSLog(#"%s", __func__);
if(self.currentDocument) {
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:[url copy], #"url",
[completionHandler copy], #"completionHandler",
nil];
[self closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(openDocumentWithContentsOfURLIfClosedAll:didCloseAll:contextInfo:)
contextInfo:(__bridge_retained void *)(info)];
} else {
[super openDocumentWithContentsOfURL:url display:displayDocument completionHandler:completionHandler];
}
}
- (void)openDocumentWithContentsOfURLIfClosedAll:(NSDocumentController *)docController
didCloseAll: (BOOL)didCloseAll
contextInfo:(void *)contextInfo
{
NSDictionary *info = (__bridge NSDictionary *)contextInfo;
if(self.currentDocument == nil)
[super openDocumentWithContentsOfURL:[info objectForKey:#"url"] display:YES completionHandler:[info objectForKey:#"completionHandler"]];
}
There's a very informative exchange on Apple's cocoa-dev mailing list that describes what you have to do in order to subclass NSDocumentController for your purposes. The result is that an existing document is closed when a new one is opened.
Something else you might consider is to mute or stop playing a document when its window resigns main (i.e., sends NSWindowDidResignMainNotification to the window's delegate), if only to avoid forcing what might seem to be an artificial restriction on the user.
I know it's been a while, but in case it helps others....
I had what I think is a similar problem, and the solution was to call the completion handler when my custom DocumentController did not open the document, e.g.:
- (void)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)displayDocument completionHandler:(void (^)(NSDocument * _Nullable, BOOL, NSError * _Nullable))completionHandler {
if (doOpenDocument) {
[super openDocumentWithContentsOfURL:url display:displayDocument completionHandler:completionHandler];
} else {
completionHandler(NULL, NO, NULL);
}
}
When I added the completionHandler(NULL, NO, NULL); it started working for more than a single shot.
I am using the AWS SDK for iOS to upload and download files to and from local hard drive to Amazon S3 storage. I am capable of making this work but I am unable to get the S3 delegate to respond properly to alert me when operations have finished or resulted in an error.
I have an array of files that I want to upload. For each file I create a NSOperation where the main routine consist mostly of:
AmazonCredentials * credentials = [[AmazonCredentials alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
putObjectRequest = [[S3PutObjectRequest alloc] initWithKey:pathCopy inBucket:[self bucket]];
putObjectRequest.filename = pathSource;
putObjectRequest.credentials=credentials;
[putObjectRequest setDelegate:s3Delegate];
Here, the delegate (s3Delegate) is created as a regular AmazonServiceRequestDelegate which should be able to fire off responses when an operation has finished. Each of my NSOperations are added to my NSOperationQueue which executes operations non-concurrently. If I use the delegate [putObjectRequest setDelegate:s3Delegate] the operations are not working. If I remove the use of the delegate the operations are performed correctly but I am unable to receive any responses to the operations as I do not have a delegate.
If I remove the use of the NSOperationQueue completely and use the [putObjectRequest setDelegate:s3Delegate] the delegate works perfectly.
My question is what am I doing wrong with using a delegate in a queue? Since the delegate is perfectly capable of performing while not in a queue could this be related to not performing on the main thread? I really want to be able to use the queue to limit the number of non-concurrent operations, however I am unable to figure this out. I hope someone has an idea of what is going on here and any example code would be greatly appreciated. Thanks!
Cheers, Trond
It seems that the aws sdk behaves asynchronously after the time you set your delegate.
So in order to have your asynchronous aws stuff work in a (asynchronous) NSOperation, you got to put some magic to wait for AWS to complete:
In your .h NSOperation file, add a boolean:
#interface UploadOperation : NSOperation <AmazonServiceRequestDelegate> {
#private
BOOL _doneUploadingToS3;
}
and in your .m file, your main method will look like this:
- (void) main
{
.... do your stuff …..
_doneUploadingToS3 = NO;
S3PutObjectRequest *por = nil;
AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY withSecretKey:SECRET_KEY];
s3Client.endpoint = endpoint;
#try {
por = [[[S3PutObjectRequest alloc] initWithKey:KEY inBucket:BUCKET] autorelease];
por.delegate = self;
por.contentType = #"image/jpeg";
por.data = _imageData;
[s3Client putObject:por];
}
#catch (AmazonClientException *exception) {
_doneUploadingToS3 = YES;
}
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!_doneUploadingToS3);
por.delegate = nil;
.... continue with your stuff ….
}
do not forget to implement your delegate methods
-(void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response
{
_doneUploadingToS3 = YES;
}
-(void)request:(AmazonServiceRequest *)request didFailWithError:(NSError *)error
{
_doneUploadingToS3 = YES;
}
-(void)request:(AmazonServiceRequest *)request didFailWithServiceException:(NSException *)exception
{
_doneUploadingToS3 = YES;
}
- (void) request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
// Do what you want
}
-(void)request:(AmazonServiceRequest *)request didReceiveResponse:(NSURLResponse *)response
{
// Do what you want
}
-(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data
{
// Do what you want
}
Note: this magic can work for any stuff that performs asynchronously but have to be implemented in a NSOperation.
I'm having trouble finding examples of the correct way to use NSError, UIAlertView, and NSErrorRecoveryAttempting together on iOS. Most of the documentation and examples I can find cover the equivalent functionality on OS X, where the relevant behaviors are integrated by Cocoa. But in iOS it seems to be necessary do do this "by hand", and I can't find good examples of how it's done.
I'd very much appreciate a few examples of best practice in using information in NSError to support recovery attempts from NSErrors reported to the user.
According to Apple's documentation:
Important: The NSError class is available on both Mac OS X and iOS. However, the error-responder and error-recovery APIs and mechanisms are available only in the Application Kit (Mac OS X).
So, I'm not sure if you can use NSErrorRecoveryAttempting even though it does appear to be defined in the documentation (it looks like this is an area of the UIKit docs that have not yet been updated after being copied from AppKit's documentation).
Here is how I handle errors in my code:
NSError *error = nil;
id result = [SomeClass doSomething:&error];
if (!result) {
NSLog(#"Do something failed: %#", error);
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"Something failed!" message:#"There was an error doing something." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] autorelease];
[alert show];
return;
}
I found a great example of this.
See the following blog post and GitHub code (including sample project) by James Beith
http://www.realmacsoftware.com/blog/cocoa-error-handling-and-recovery
https://github.com/realmacsoftware/RMErrorRecoveryAttempter
I was able to successfully use this on the iPhone simulator.
I'm trying to mirror AppKit's error handling mechanism in UIKit, mainly because I want to take advantage of the responder chain to forward errors upwards. I haven't tested this fully, but at the moment it's looking like below.
It reflects AppKit pretty closely, but the will/did hooks can be overridden to perform custom error presentation and recovery respectively. The default behaviour is to show a UIAlertView for presentation and use a psuedo-NSErrorRecoveryAttempting object for recovery.
#implementation UIResponder (ErrorHandling)
- (void)presentError:(NSError *)error
completion:(void (^)(BOOL recovered))completion
{
if (nil == (error = [self willPresentError:error])) {
return;
}
if (self.nextResponder) {
[self.nextResponder presentError:error completion:completion];
return;
}
// Code to create and show UIAlertView
// e.g. https://github.com/jayway/CWUIKit/blob/master/Classes/UIAlertView%2BCWErrorHandler.m
// The UIAlertViewDelegate calls didPresentError...
}
/*
Override to customise the error object as in AppKit.
You can also perform your own error presentation, and return nil to terminate the default handling.
Custom error presentation UI should still call didPresentError... when dismissed
*/
- (NSError *)willPresentError:(NSError *)error
{
return error;
}
/*
Override to perform custom error recovery.
*/
- (void)didPresentError:(NSError *)error optionIndex:(NSInteger)optionIndex completion:(void (^)(BOOL recovered))completion
{
id recoveryAttempter = [error recoveryAttempter];
if ([recoveryAttempter respondsToSelector:#selector(attemptRecoveryFromError:optionIndex:completion:)]) {
[recoveryAttempter attemptRecoveryFromError:error optionIndex:optionIndex completion:completion];
}
}
#end