Siri : IntentHandler not behaving correctly with device Touch ID - payment

What is the right approach to handle device LocalAuthentication with NSIntent? Currently it is not showing payment sent pop up in Siri INSendPaymentIntent.
- (void)handleSendPayment:(INSendPaymentIntent *)intent
completion:(void (^)(INSendPaymentIntentResponse *response))completion {
__block INSendPaymentIntentResponse *siriResponse;
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
// Check if device supports TouchID
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// TouchID supported, show it to user
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Unlock using Touch ID to transaction”
reply:^(BOOL success, NSError *error) {
if (success) {
// This action has to be on main thread and must be synchronous
dispatch_async(dispatch_get_main_queue(), ^{
// Payment handling code goes here
});
}
else if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
siriResponse = [[INSendPaymentIntentResponse alloc] initWithCode:INSendPaymentIntentResponseCodeSuccess userActivity:nil];
});
}
}];
}
}
If I remove Local Authentication payment works just fine!!

Related

Core Data Parent/Child context save fail

I setup a background thread with the Parent/Child model. Essentially the context save is failing.
Here is my setup. In the AppDelegate i've setup the _managedObjectContext with the NSMainQueueConcurrencyType:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];//[[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
In my data loading class I setup the parent/child mocs here to perform the work on the background thread:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[moc setParentContext:mainMOC];
[moc setUndoManager:nil];
When the json data has completed I attempt to peform a save operation with the following macro:
#define SAVE_MOC { NSError *error; \
if (![moc save:&error]) { NSLog(#"Sub MOC Error"); } \
[mainMOC performBlock:^{ NSError *e = nil; if (![mainMOC save:&e]) {
NSLog(#"Main MOC Error %#",error.localizedDescription);}}];}
Also when i've completed the data load I jump back on the main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"<---- complete CS sub moc! ---->");
//this fires ok
});
So, from my SAVE_MOC macro i just get a simple error:
Main MOC Error (null)
Let me know if I can provide more info. I'm very new to multi-threading and trying to get a better handle on this approach.
Thanks,
Josh
In my data loading class I setup the parent/child mocs here to perform
the work on the background thread:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
You should not do that. Do this instead.
NSManagedObjectContext *mainMOC = self.managedObjectContext;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
Make sure you access the MOC in a performBlock. For example,
[moc performBlock:^{
// Anything at all involving this MOC or any of its objects
}];
When the json data has completed I attempt to peform a save operation
with the following macro:
Consider saving with something like this. Your completion block will be called when the save has finished.
- (void)saveMOC:(NSManagedObjectContext*)moc
completion:(void(^)(NSError *error))completion {
[moc performBlock:^{
NSError *error = nil;
if ([moc save:&error]) {
if (moc.parentContext) {
return [self saveMOC:moc.parentContext completion:completion];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}
}];
}
[self saveMOC:moc completion:^(NSError *error) {
// Completion handler is called from main-thread, after save has finished
if (error) {
// Handle error
} else {
}
}];
EDIT
This code will crash if moc.parentContext is main concurrency type. –
Mundi
There is no inherent reason that the code I posted should cause a crash with a parent MOC of NSMainQueueConcurrencyType. It has supported being a parent context ever since parent/child was added to Core Data.
Maybe I was missing a typo, so I copy/paste saveMOC:completion: straight from this answer, and wrote the following test helper.
- (void)testWithChildConcurrencyType:(NSManagedObjectContextConcurrencyType)childConcurrencyType
parentConcurrencyType:(NSManagedObjectContextConcurrencyType)parentConcurrencyType {
NSAttributeDescription *attr = [[NSAttributeDescription alloc] init];
attr.name = #"attribute";
attr.attributeType = NSStringAttributeType;
NSEntityDescription *entity = [[NSEntityDescription alloc] init];
entity.name = #"Entity";
entity.properties = #[attr];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
model.entities = #[entity];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL];
NSManagedObjectContext *parent = [[NSManagedObjectContext alloc] initWithConcurrencyType:parentConcurrencyType];
parent.persistentStoreCoordinator = psc;
NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:childConcurrencyType];
child.parentContext = parent;
NSManagedObject *obj = [NSEntityDescription insertNewObjectForEntityForName:#"Entity" inManagedObjectContext:child];
[obj setValue:#"value" forKey:#"attribute"];
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:#"save from %# to %# finished", concurrencyTypeString(childConcurrencyType), concurrencyTypeString(parentConcurrencyType)]];
[self saveMOC:child completion:^(NSError *error) {
// Verify data saved all the way to the PSC
NSManagedObjectContext *localMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
localMoc.persistentStoreCoordinator = psc;
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
XCTAssertEqualObjects(#"value", [[[localMoc executeFetchRequest:fr error:NULL] firstObject] valueForKey:#"attribute"]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
And then, I wrote a test for each possible parent/child relationship.
- (void)testThatDoingRecursiveSaveFromPrivateToPrivateWorks {
[self testWithChildConcurrencyType:NSPrivateQueueConcurrencyType
parentConcurrencyType:NSPrivateQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromPrivateToMainWorks {
[self testWithChildConcurrencyType:NSPrivateQueueConcurrencyType
parentConcurrencyType:NSMainQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromMainToPrivateWorks {
[self testWithChildConcurrencyType:NSMainQueueConcurrencyType
parentConcurrencyType:NSPrivateQueueConcurrencyType];
}
- (void)testThatDoingRecursiveSaveFromMainToMainWorks {
[self testWithChildConcurrencyType:NSMainQueueConcurrencyType
parentConcurrencyType:NSMainQueueConcurrencyType];
}
So, what am I missing?
As I write this, I am reminded of a 360iDev presentation where the presenter said that you can't call performBlock on a NSMainQueueConcurrencyType context. At the time, I thought he just misspoke, meaning confinement, but maybe there is some confusion in the community about this.
You can't call performBlock on a NSConfinementConcurrencyType MOC, but performBlock is fully supported for NSMainQueueConcurrencyType.

Save data with Magical Record inside a block

I am retrieving some data from an API resource and I want to store the result inside my City entity using Magical Record and when the process finish, reload a tableView in my ViewController with the results.
All is fine but when I start the app for the first time,dowload process is started and the data is saved in core data.
but the table view in my ViewControllers is empty.
If I launch the app after the first time
the tableView refresh correctly.
I don't know if the problem is in threads... Can anybody help me?
ViewController :
Here I start the request. When block is called, I store cities array and reload tableView
- (void)getCitiesFromDataStore {
[[APIManager sharedManager] getCitiesWithCompletion:^(NSArray *cities) {
_dataSourceArray = cities;
[self.citiesTableView reloadData];
} failure:^(NSError *error) {
NSLog(#"%#",error.localizedDescription);
}];
}
APIMAnager
- (void)getCitiesWithCompletion:(void (^)(NSArray *))succesBlock
failure:(void (^)(NSError *))errorBlock
{
NSArray *cachedCities = [City findAllCities];
if ([cachedCities count] == 0) {
[self GET:#"cities" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *results = responseObject[#"cities"];
[City MR_importFromArray:results];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
NSArray *cities = [City findAllCities];
succesBlock(cities);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];
return;
}
// Si ya hay ciudades almacenadas en CoreData, devuelvo el
// succesblock con las ciudades de CoreData
succesBlock(cachedCities);
}
I have a Category also to manage actions with the City entity
City+DBOperations
+ (NSArray *)findAllCities
{
NSArray *cities = [City MR_findAll];
return cities;
}
I know you said you resolved it, but for others who might be coming here another thing you could try is wrapping the import in a saveWithBlock:completion: and do your find in the completion block.
Also make sure you know which context each method is using. It is often helpful to be explicit about that.
Therefore you could change it to (this is untested, but should give you the concept):
[self GET:#"cities" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *results = responseObject[#"cities"];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[City MR_importFromArray:results inContext:localContext];
} completion:^(BOOL contextDidSave, NSError *error) {
NSArray *cities = [User MR_findAllInContext:[NSManagedObjectContext MR_defaultContext]];
succesBlock(cities);
}];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];

iOS pjsip 2.2 loud speaker switch fails

During the call I try to switch voice from internal speaker to Loud speaker on iOS device using pjsip 2.2 library. It returns TRUE as success, but physically it doesn't change sound destination.
I use the next code
- (BOOL)setLoud:(BOOL)loud {
if (loud) {
#try {
pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
pj_status_t pj_status = pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
&route, PJ_TRUE);
if (pj_status == PJ_SUCCESS) {
return YES;
}
else
{
return NO;
}
}
#catch (NSException *exception) {
return NO;
}
} else {
#try {
pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
pj_status_t pj_status = pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
&route, PJ_TRUE);
if (pj_status == PJ_SUCCESS) {
return YES;
}
else
{
return NO;
}
}
#catch (NSException *exception) {
return NO;
}
}
}
Could you suggest how can we make this work?
With the introduction of iOS 7, you should now be using AVAudioSession to handle any audio management. It took me a long time to finally get this to work but I finally figured out the problem of why my audio was not automatically routing to my iPhone Speaker. The problem is that when you answer a call, pjsip was automatically overriding the AVAudioSessionPortOverride I was performing before the call is answered. To tackle this problem, you simply just have to override the output audio port AFTER answering the call.
To make my VoIP application work efficiently with the background mode, I decided to handle the audio routing in a custom callback method named on_call_state. This method, on_call_state, is called by pjsip when a call state has changed. As you can read here, http://www.pjsip.org/pjsip/docs/html/group__PJSIP__INV.htm, there are many different flags you can check for when a call state has changed. The states I used in this example are PJSIP_INV_STATE_CONNECTING and PJSIP_INV_STATE_DISCONNECTED.
PJSIP_INV_STATE_CONNECTING is called when a audio call connects to another peer.
PJSIP_INV_STATE_DISCONNECTED is called when a audio call ends with another peer.
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id,
(int)ci.state_text.slen,
ci.state_text.ptr));
if (ci.state == PJSIP_INV_STATE_CONNECTING) {
BOOL success;
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&error];
if (!success) NSLog(#"AVAudioSession error setCategory: %#", [error localizedDescription]);
success = [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
if (!success) NSLog(#"AVAudioSession error overrideOutputAudioPort: %#", [error localizedDescription]);
success = [session setActive:YES error:&error];
if (!success) NSLog(#"AVAudioSession error setActive: %#", [error localizedDescription]);
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
BOOL success;
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
success = [session setActive:NO error:&error];
if (!success) NSLog(#"AVAudioSession error setActive: %#", [error localizedDescription]);
}
}

FBLoginView: login isn't performed

I'm adding FBLoginView to my ViewController < FBLoginViewDelegate >:
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
All the necessary fields in plist (FacebookAppID, FacebookDisplayName, URL Schemes) are all set according to the tutorial. The facebook app is also configured according to the tutorial (bundle ID is set, Facebook login is enabled).
But the login still isn't performed. When I press on "log in", I get redirected to the browser with facebook login, but when it's finished, I'm not logged in the app (loginViewFetchedUserInfo:user: isn't called, "log in" hasn't changed to "log out").
What can be the problem?
Everything worked after I implemented the following in the AppDelegate.m (taken from one of the official examples):
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
// We have a valid session
//NSLog(#"User session found");
[FBRequestConnection
startForMeWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
if (!error) {
self.loggedInUserID = user.id;
self.loggedInSession = FBSession.activeSession;
}
}];
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
/*
* Opens a Facebook session and optionally shows the login UX.
*/
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
return [FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
[self sessionStateChanged:session
state:state
error:error];
}];
}
/*
*
*/
- (void) closeSession {
[FBSession.activeSession closeAndClearTokenInformation];
}
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}
You need to add the following to the app delegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// Call FBAppCall's handleOpenURL:sourceApplication to handle Facebook app responses
BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
// You can add your app-specific url handling code here if needed
return wasHandled;
}
You may need to implement the
(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url method
Be sure to call:
return [FBSession.activeSession handleOpenURL:url];
When applicable.
#Sergey: Are you want to open FBDialog on your native app or in browser? If you to want open in your native app then use "FBSessionLoginBehaviorForcingWebView". Here is my code that I am using:
NSArray *permission = [NSArray arrayWithObjects:kFBEmailPermission,kFBUserPhotosPermission, nil];
FBSession *session = [[FBSession alloc] initWithPermissions:permission];
[FBSession setActiveSession: [[FBSession alloc] initWithPermissions:permission] ];
[[FBSession activeSession] openWithBehavior:FBSessionLoginBehaviorForcingWebView completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
switch (status) {
case FBSessionStateOpen:
[self yourmethod];
break;
case FBSessionStateClosedLoginFailed: {
// prefer to keep decls near to their use
// unpack the error code and reason in order to compute cancel bool
NSString *errorCode = [[error userInfo] objectForKey:FBErrorLoginFailedOriginalErrorCode];
NSString *errorReason = [[error userInfo] objectForKey:FBErrorLoginFailedReason];
BOOL userDidCancel = !errorCode && (!errorReason || [errorReason isEqualToString:FBErrorLoginFailedReasonInlineCancelledValue]);
if(error.code == 2) {
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"FBAlertTitle"
message:#"FBAuthenticationErrorMessage"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[errorMessage performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
errorMessage = nil;
}
}
break;
// presently extension, log-out and invalidation are being implemented in the Facebook class
default:
break; // so we do nothing in response to those state transitions
}
}];
permission = nil;
or you want to open in browser then use following :
In your .h file
#import <FacebookSDK/FacebookSDK.h> and add FBLoginViewDelegate delegate
In you .m file
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
// use following delegate methods
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// first get the buttons set for login mode
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
// here we use helper properties of FBGraphUser to dot-through to first_name and
// id properties of the json response from the server; alternatively we could use
// NSDictionary methods such as objectForKey to get values from the my json object
NSLog(#"userprofile:%#",user);
}
- (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView {
//BOOL canShareAnyhow = [FBNativeDialogs canPresentShareDialogWithSession:nil];
}
- (void)loginView:(FBLoginView *)loginView handleError:(NSError *)error {
// see https://developers.facebook.com/docs/reference/api/errors/ for general guidance on error handling for Facebook API
// our policy here is to let the login view handle errors, but to log the results
NSLog(#"FBLoginView encountered an error=%#", error);
}

UIManagedDocument - How to deal with UIDocumentStateSavingError?

I am working on my first iCloud App. After working for a while the app cannot access a UIManagedDocument any more due to an "UIDocumentStateSavingError". Is there any way to actually find out what error occurred?
This is my code to create the UIManagedDocument:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (iCloudURL == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[self iCloudNotAvailable];
});
return;
}
iCloudDocumentsURL = [iCloudURL URLByAppendingPathComponent:#"Documents"];
iCloudCoreDataLogFilesURL = [iCloudURL URLByAppendingPathComponent:#"TransactionLogs"];
NSURL *url = [iCloudDocumentsURL URLByAppendingPathComponent:#"CloudDatabase"];
iCloudDatabaseDocument = [[UIManagedDocument alloc] initWithFileURL:url];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
NSString *name = [iCloudDatabaseDocument.fileURL lastPathComponent];
[options setObject:name forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setObject:iCloudCoreDataLogFilesURL forKey:NSPersistentStoreUbiquitousContentURLKey];
iCloudDatabaseDocument.persistentStoreOptions = options;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentContentsChanged:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:iCloudDatabaseDocument.managedObjectContext.persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentStateChanged:) name:UIDocumentStateChangedNotification object:iCloudDatabaseDocument];
if ([[NSFileManager defaultManager] fileExistsAtPath:[iCloudDatabaseDocument.fileURL path]]) {
// This is true, the document exists.
if (iCloudDatabaseDocument.documentState == UIDocumentStateClosed) {
[iCloudDatabaseDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
[self documentConnectionIsReady];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self connectionError:iCloudConnectionErrorFailedToOpen];
});
}
}];
} else if (iCloudDatabaseDocument.documentState == UIDocumentStateNormal) {
...
}
} else {
...
}
});
The Document already exists and thus openWithCompletionHandler: is called on the document. This fails and the UIDocumentStateChangedNotification is fired which shows a document states of 5:
UIDocumentStateClosed and
UIDocumentStateSavingError
After this the completion block gets called. What is correct way to proceed from here? Is there any way to find out what went wrong and what kind of error occurred?
I tried to re-open the document in the completion block but the result is the same.
I guess I could solve the problem by just deleting the file and recreate it. But this is obviously not an option once the app will be out in the store. I would like to know what is going wrong and give the user an appropriator way to handle the problem.
I already checked other questions here handling the UIDocumentStateSavingError (there a not a lot of them) but the seem not to be applicable for the problem here.
Any idea how I can find out what the problem is? I cannot belive that the API tells you "Something went wrong during saving but I will not tell you what!"
You can query the documentState in the completion handler. Unfortunately, if you want the exact error, the only way to get it is to subclass and override handleError:userInteractionPermitted:
Maybe something like this would help (typed freehand without compiler)...
#interface MyManagedDocument : UIManagedDocument
- (void)handleError:(NSError *)error
userInteractionPermitted:(BOOL)userInteractionPermitted;
#property (nonatomic, strong) NSError *lastError;
#end
#implementation MyManagedDocument
#synthesize lastError = _lastError;
- (void)handleError:(NSError *)error
userInteractionPermitted:(BOOL)userInteractionPermitted
{
self.lastError = error;
[super handleError:error
userInteractionPermitted:userInteractionPermitted];
}
#end
Then in you can create it like this...
iCloudDatabaseDocument = [[UIManagedDocument alloc] initWithFileURL:url];
and use it in the completion handler like this...
[iCloudDatabaseDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
[self documentConnectionIsReady];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self connectionError:iCloudConnectionErrorFailedToOpen
withError:iCloudDatabaseDocument.lastError];
});
}
}];
Based on #JodyHagins excellent snippet, I have made a UIDocument subclass.
#interface SSDocument : UIDocument
- (void)openWithSuccess:(void (^)())successBlock
failureBlock:(void (^)(NSError *error))failureBlock;
#end
#interface SSDocument ()
#property (nonatomic, strong) NSError *lastError;
#end
#implementation SSDocument
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted {
self.lastError = error;
[super handleError:error userInteractionPermitted:userInteractionPermitted];
}
- (void)clearLastError {
self.lastError = nil;
}
- (void)openWithSuccess:(void (^)())successBlock failureBlock:(void (^)(NSError *error))failureBlock {
NSParameterAssert(successBlock);
NSParameterAssert(failureBlock);
[self clearLastError];
[self openWithCompletionHandler:^(BOOL success) {
if (success) {
successBlock();
} else {
NSError *error = self.lastError;
[self clearLastError];
failureBlock(error);
}
}];
}
#end

Resources