NSSharingService performWithItems Hangs

446 views Asked by At

I had a working share routine and now it is broken. Hadn't checked it, or modified it, for some time and now find that it is inoperable. When I call

[sharingService performWithItems:[NSArray arrayWithObject:itemProvider]];

I get a share sheet displayed. It shows the current members of the share. The form is inoperable and will not accept any input or taps. I cannot add, remove or stop sharing altogether. When I close the form, my app is hung up and will not respond or take focus. I have to kill the app and reopen to get it working again.

This used to work fine, within the last few months. I hadn't changed anything so I am very surprised by new problem.

I am adding my code for creating share here:

NSString *shareOption = [[NSUserDefaults standardUserDefaults] objectForKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
if ([shareOption isEqualToString:TTICloudKitShareOwnerService]) {
    CDEZipCloudFileSystem *zipFile = (CDEZipCloudFileSystem *)_cloudFileSystem;
    CDECloudKitFileSystem *fileSystem = (CDECloudKitFileSystem *)zipFile.cloudFileSystem;

    NSItemProvider *itemProvider = [[NSItemProvider alloc] init];
    [itemProvider registerCloudKitShare:fileSystem.share container:fileSystem.container];

    NSSharingService *sharingService = [NSSharingService sharingServiceNamed:NSSharingServiceNameCloudSharing];
    sharingService.subject = @"Share Workforce Data";
    sharingService.delegate = self;
    if ([sharingService canPerformWithItems:[NSArray arrayWithObject:itemProvider]]) {
        [sharingService performWithItems:[NSArray arrayWithObject:itemProvider]];

    // This is the point at which the Apple UI is presented but inoperable.
    // No changes can be made to the share.
    // The only way to dismiss the dialog is to quit or press escape.
    // Upon dismissal the app is either crashed or hung up.
    // Quitting the app and restart is only option to use the app again.
    // If not run from Xcode, requires force quit.

    }
} else {
    NSLog(@"Is Shared Ensemble");
    NSAlert *alert = [[NSAlert alloc] init];
    [alert addButtonWithTitle:@"Stop Share"];
    [alert addButtonWithTitle:@"Cancel"];
    [alert setMessageText:@"Shared Data Options"];
    [alert setInformativeText:@"You are participating in a shared file. Stop sharing will remove your participation and reset your data. You will no longer participate or have access to the shared information."];
    [alert setAlertStyle:NSAlertStyleWarning];

    if ([alert runModal] == NSAlertFirstButtonReturn) {
        [alert setInformativeText:@"Are you sure? You will no longer have access to shared data. You will need the owner of the share to resend an invitation to join the share."];
        if ([alert runModal] == NSAlertFirstButtonReturn) {
            // This actually does not remove user from sharing as intended.
            // I am sure that is my own implementation incomplete though.
            NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
            [defaults setNilValueForKey:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT];
            [defaults setObject:TTICloudKitShareOwnerService forKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
            [defaults synchronize];
            [self disconnectFromSyncServiceWithCompletion:^{
                // TODO: Need to wipe the existing Core Data info here. Leave them with no access to shared data.
                //      Also need to remove self from the share?
                [self reset];
                [self setupEnsemble];
            }];
        }
    }
}

Creating share and sending worked flawlessly and I'd been developing app and testing live. Currently my test is shared with two other users and still works. In fact I can't seem to find a way to stop sharing with those users or in any way alter the current share at all.

This is the NSCloudSharingServiceDelegate code:

-(NSCloudKitSharingServiceOptions)optionsForSharingService:(NSSharingService *)cloudKitSharingService shareProvider:(NSItemProvider *)provider
{
  return NSCloudKitSharingServiceAllowPrivate | NSCloudKitSharingServiceAllowReadWrite;
}

-(void)sharingService:(NSSharingService *)sharingService willShareItems:(NSArray *)items
{
  DLog(@"Will Share Called with items:%@",items);
}

-(void)sharingService:(NSSharingService *)sharingService didShareItems:(NSArray *)items
{
  DLog(@"Did share called");
}

-(void)sharingService:(NSSharingService *)sharingService didFailToShareItems:(NSArray *)items error:(NSError *)error
{
  DLog(@"Sharing service failed to share items, %@-", error);
  if (error.code == NSUserCancelledError) return;
  DLog(@"Failed to share, error- %@", error.userInfo);
  [self disconnectFromSyncServiceWithCompletion:^{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT forKey:kSet_CLOUD_SERVICE_USER_DEFAULT];
    [defaults setNilValueForKey:kSet_CLOUDKIT_SHARE_OWNER_DEFAULT];
    [defaults synchronize];
  }];
}

It is apparent that I am one of the very few who find this to be important as I have scoured the webs and found almost nobody discussing it. Apple documentation is just about nil.

This is a screenshot of the Apple UI which is not working:

Inoperable Dialog for CKShare Process

1

There are 1 answers

0
Dean Davids On

I am posting this as an answer, is more like a work around that I have managed to get working. Still no answer as to why the Apple UI does not respond.

See code for inviting participants without the Apple UI.

-(void)addParticipantWithEmail:(NSString *)email toShare:(CKShare *)share inContainer:(CKContainer *)container
{
    [container discoverUserIdentityWithEmailAddress:(email) completionHandler:^(CKUserIdentity * _Nullable userInfo, NSError * _Nullable error) {
        if (!userInfo || error) {
            NSLog(@"Participant was not found for email %@", email);
            if (error) {
                NSLog(@"Error: %@", error.userInfo);
            } else {
                NSLog(@"No error was provided");
            }

            // abort
            return;
        }
        CKFetchShareMetadataOperation *fetchMetaDataOperation = [[CKFetchShareMetadataOperation alloc] initWithShareURLs:[NSArray arrayWithObject:share.URL]];
        fetchMetaDataOperation.shouldFetchRootRecord = YES;
        [fetchMetaDataOperation setPerShareMetadataBlock:^(NSURL * _Nonnull shareURL, CKShareMetadata * _Nullable shareMetadata, NSError * _Nullable error) {
            CKRecord *root = shareMetadata.rootRecord;

            if (!root) {
                NSLog(@"There was an error retrieving the root record- %@", error);
            } else {
                NSLog(@"Root is %@", root);
                NSLog(@"/n");
            }

            CKUserIdentityLookupInfo *info = userInfo.lookupInfo;
            CKFetchShareParticipantsOperation *fetchOperation = [[CKFetchShareParticipantsOperation alloc] initWithUserIdentityLookupInfos:[NSArray arrayWithObject:info]];
            [fetchOperation setShareParticipantFetchedBlock:^(CKShareParticipant * _Nonnull participant) {
                participant.permission = CKShareParticipantPermissionReadWrite;
                [share addParticipant:participant];
                CKModifyRecordsOperation *modifyOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:[NSArray arrayWithObjects:root, share, nil] recordIDsToDelete:nil];
                modifyOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
                [modifyOperation setPerRecordCompletionBlock:^(CKRecord * _Nonnull record, NSError * _Nullable error) {
                    if (error) {
                        DLog(@"Error modifying record %@. UserInfo: %@", record, error.userInfo);
                    } else {
                        DLog(@"No Error Reported in Modify Operation");
                    }
                }];
                [container.privateCloudDatabase addOperation:modifyOperation];
            }];
            [fetchOperation setFetchShareParticipantsCompletionBlock:^(NSError * _Nullable operationError) {
                if (operationError) {
                    NSLog(@"There was en error in the fetch operation- %@", operationError.userInfo);
                    // Error may be a network issue, should implement a retry and possibly a limit to how many times to run it
                }
            }];
            [container addOperation:fetchOperation];
        }];
        [container addOperation:fetchMetaDataOperation];
    }];
}

It seems now, if I pass an email address to this function they are successfully invited to share, provided the user is in my contacts and has allowed discoverability.

I send the user the link to the share manually via iMessage at this point. Copied the URL from the console. My intent is to provide my own forms to handle that now.

On receiving link, I use Ensembles method:

CDECloudKitFileSystem acceptInvitationToShareWithMetadata:metadata completion:^(NSError *error)

This code didn't seem to work, accepting invites was failing initially. Without having changed anything, the accepting shares started to work. I am not sure why the initial fails.