UICloudSharingController does not work for Core Data with CloudKit
Asked Answered
N

2

14

In the summer, Apple published an informative sample app on sharing objects between iCloud users using Core Data, CloudKit and UICloudSharingController in SwiftUI.

However, adding more participants using UICloudSharingController does not appear to work when used for Core Data with CloudKit.

MRE:
See the Apple sample app linked above. The same problem also appears when using other sample apps, like the one from RayW. The problem does not appear in samples that use pure CloudKit, such as this one by Apple.

Reproduction:

  1. Create new share,
  2. Manage share with UICloudSharingController,
  3. Share With More People,
  4. Share using Messages or other service. Succeeds on first attempt, fails on subsequent attempts.

Expectation:
We can use UICloudSharingController to add new participants, using Messages, Mail or other platforms. The link will display correctly on all devices.

Reality:
On iOS16+, attempting to share via Messages engages the "Collaboration" framework and leads to an alert: "An Error Occurred. Unable to start collaboration" (see Image 1). A console warning appears (see below). Triggering this error breaks ANY further shares - the link now cannot be created for Mail and other platforms either (see Image 2). Furthermore, if the first attempt succeeds, the link does not appear correctly on the receiving device (see Image 3).

After further testing, UICloudSharingController also fails in iOS15 - it just dismisses the sheet rather than throw an alert on a compact device. UICloudSharingController is definitely bugged when showing an existing share.

Console logs:
The following console message appears when this issue happens for the first time:

  • CoreDataCloudKitShare[3672:1242159] systemSharingUIDidSaveShareBlock received error: <CKError 0x28314d8c0: "Server Record Changed" (14/2004); server message = "client oplock error updating record"; op = 134D57570A63DF3A; uuid = 8F070F8B-0AC0-4FFE-A52D-154BCBF3196C; container ID = “containerID>

Where "containerID" is the CKContainer ID, like “iCloud.com.company.samples.CoreDataCloudKitShare”. The message does not appear on subsequent attempts to add more people.

Question:
How can this be resolved, so that we can share Core Data records between users using CloudKit and UICloudSharingController?

Images

EDITs:

  • Other samples of sharing using Core Data and CloudKit exhibit the same problem. Also, console shows a warning when the issue first happens. Post was edited to reflect this.
  • Question was also posed in Apple Dev Forums
  • Feedback submitted at FB11623246.
  • Added conclusions after further testing
  • 24 October 2022: I thought the issue was resolved in iOS16.0.3, but it continues to manifest. It does not matter whether UICloudSharingController is invoked using UIViewController or UIViewControllerRepresentable.
  • 10 November 2022: The issue appears in both Development and Production environments (tested via TestFlight)
  • 15 November 2022: In Ask Apple office hours, this was brought up to the attention of an Apple engineer, who confirmed this as a regression.
  • 25 November 2022: A DTS has been opened. This is now confirmed as a known bug with high priority.
  • 26 December 2022: The bug remains in iOS16.2. An attempt was made to use SWCollaborationView as an alternative to managing collaboration in iOS16. The sharing workflow indeed goes through even on subsequent shares. However, SWCollaborationView does not appear to be compatible with SwiftUI (ugh). See associated SO question
  • 17 January 2023: SWCollaborationView is confirmed as NOT compatible with SwiftUI. FB submitted at FB11941664, please submit FB as well.
  • 25 January 2023: Unfortunately, the issue persists in iOS 16.3.
  • 15 February 2023: Unfortunately, the issue persists in iOS 16.3.1.
  • 2 March 2023: Apple says the issue should be fixed in the following configuration. I have not yet tested this (maybe next week). If anyone could confirm it would be great.
    • Xcode 14.3 beta 2 (14E5207e)
    • iOS 16.4 beta 2 (20E5223e)
    • iPadOS 16.4 beta 2 (20E5223e)
    • macOS 13.3 beta 2 (22E5230e)
  • 12 March 2023: I tested the above configuration. UICloudSharingController now works as intended. HOWEVER, attempting to add a photo to an existing share where user is Participant with Write privileges results in an error. Apple was notified, waiting for a response. Console output: shareObject(_:to:completionHandler:): Failed to share an object: Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=NSPersistentCloudKitContainer does not support sharing objects across persistent stores. Objects must first be assigned to the correct persistent store (private, or shared as the case may be), so that they can be moved in to the correct record zone for sharing.})
  • 14 March 2023: I re-tested and confirmed the above issue using a fresh fork of the Apple Sample app. If you'd like to reproduce the issue, here are the steps I used
  • 27 April 2023: After some back and forth with Apple, I managed to resolve the issues described in this post, and built a working app with Core Data and CloudKit sharing. On their side, Apple updated the sample app. Mission accomplished.
Navicular answered 28/9, 2022 at 22:40 Comment(17)
Did you try to wrap it inside an UIViewRepresentable to make it SwiftUI compatible?Pricecutting
Yes. Initially I followed the Apple sample, which actually invokes UICloudSharingController using UIViewController and UIWindowScene. Having discovered the bug, I tried re-implementing UICloudSharingController using UIViewRepresentable. It made no difference.Navicular
Thanks for filing this and keeping it updated. I am seeing this pretty consistently on iOS 16.2, with one exception: if my app on a device had previously initiated a share on iOS 15, it continues to work. It's truly bizarre and truly problematic.Consulate
Interesting, thank you. The ball is now in Apple's court, they will hopefully fix it soon. Until then, no sharing for me.Navicular
I am still seeing the issue with iOS 16.3, so it appears we are stuck with this for a while yet.Consulate
This afternoon I tried building a set of views from scratch that implemented sharing. Bad news: still doesn’t work. I think the problem is lower than the Audi controller. It appears to me as if the share itself is actually not getting setup correctly for CoreData objects.Consulate
I have tried this repo from Apple github.com/apple/sample-cloudkit-zonesharing on a device that reliably has the error and it worked correctly. Thinking it might be core data specific, I've attempted to create the zone share using just CloudKit off of the underlying CKRecord for my CD entity. Sadly it still appears to fail for me. Here's the code gist.github.com/stanlemon/0ea042d084799c9d5acfc3aefa07b428Consulate
@Consulate indeed, as noted in my question, the problem does not appear in samples that use pure CloudKit, i.e. without Core Data.Navicular
Apple just let me know the issue should be resolved in the following configuration: Xcode 14.3 beta 2 (14E5207e), iOS 16.4 beta 2 (20E5223e), iPadOS 16.4 beta 2 (20E5223e), macOS 13.3 beta 2 (22E5230e). I haven't tested this yet.Navicular
I can confirm the above is correct, compiling with XC 14.3 Beta 2 on iOS 16.4 beta 2 and iOS 16.3.1Frug
Update: I've pushed a build to TestFlight using XC 14.3 Beta 2 and I can confirm this works in production as well, including on iOS 16.3.1 and iPadOS 16.3.1Frug
thank you @dezinezync. I am still testing, and struggling to add objects to an existing share from participant side. It results in an error and console output. It might be necessary to deep copy the object to the shared store, not sure yet. But I don't remember having this issue in prior releases.Navicular
@blueFox I haven't run into this error (yet!), and adding objects to an existing share works without any issue. persistenceContainer.share([managedRootObject], to: nil) moves the entire object graph to the shared store, if it isn't already present in it. You should be able to verify which store the object is being fetched/used from at runtime as well. "Objects must first be assigned to the correct persistent store (private, or shared as the case may be)" It's odd that the error mentions the private store here.Frug
@Frug the mention of the private store is fine, I think. If you add objects as Owner, they will remain in the private store (this works for me). However, when you add objects as Participant, they must be assigned to the shared store (= your access window to another user's (Owner's) private store. Even so, I'm not sure how to overcome this atm.Navicular
@Frug I can reliably reproduce the issue with the Apple Sample app. Here are the steps I used, if you'd like to test itNavicular
@blueFox hmm... you're right, I am able to reproduce it with the sample app and your steps to replicate the issue. What's odd is: I tried similar steps in my app but could not get NSPersistentCloudKitContainer to log this error. Everything seems to just work.Frug
@Frug ok, thank you for confirming. If you find what could have caused your app to work correctly, it would be great to know! In the meanwhile, I sent all the info to Apple as part of the TSI. Let's hope they will provide a solution before the iOS16.4 public release.Navicular
N
3

As of 27 April 2023 and iOS 16.4, the issues described in the post were resolved by Apple.

After some back and forth with Apple in my TSI, I managed to build a working app with Core Data and CloudKit sharing.

On their side, Apple updated their sample app to reflect the issues discussed and the updates made to UICloudSharingController. Use the same link to check it out.

I would like to end this by saying that Apple engineers were very helpful in solving this and provided me with lots of tips. I am grateful for their assistance.


For others: you might struggle with adding objects to other user's CKShare (i.e. into the shared persistent store). This is a response from Apple on this that set me on the right track:

"The way to support the use case today is to relate the photo to an object that is already tied to a share (a shared tag, for example). That way, Core Data assigns the photo to the right record zone when saving it, just like what it does when you add a new tag to a shared photo from the participant side. It may be reasonable to introduce something like albums so users can tie a photo to a shared album."

Also, if you plan to show the same object between shares (e.g. a single Photo that might show up in multiple Albums), do not forget that relationships cannot be established across shared record zones. So you cannot have a single Photo with a relationship to multiple Albums.

Navicular answered 23/10, 2022 at 15:49 Comment(5)
Congratulations @blueFox, your persistence ;) on the subject has paid-off for the community as well. Your efforts are appreciated.Frug
Thank you @blueFox for pushing this, after close to 6 month pushing Apple on this, I had given up! Thanks for the comprehensive feedback. It looks that indeed the bug is fixed now. It looks as well they fixed the stopSharing "feature" that was present before. Now when I stop a Share, the shared zone is kept, just not shared, which makes it a lot simpler for us and doesn't require us to clone the deleted objects that were present in the share to prevent them from being lost.Suber
@Suber I'm glad I'm not the only one who benefits! Yeah I'd be careful about these "keeping of zone" mechanisms. Apparently there's a limit on the total number of zones per container. I'm still discussing with Apple because I want to double-check that we won't hit that limit using the updated functionality.Navicular
@blueFox Yes maybe they should provide an option to choose to keep or delete completely the zone when removing a share, or remove the limit. The cloning strategy itself was far from ideal though and looked more like an ugly workaround, not giving much confidence due to potential data loss and it could be triggered just by removing the last person from a share. I guess slowly we'll eventually end up with a usable API for sharing.Suber
@blueFox For reference I posted a new question regarding purging records in a shared zone here Purging shared records new behaviour which behavior seems to have changed too afaikSuber
S
2

Being dealing with both UISharingController and SWCollaborationView for a few month now, I came across this bug (and many others) multiple times wether on my own code or from the Apple samples available.

Calling persistUpdatedShare(_:in:completion:) seems to both save an updated version to the Cloud AND update the locally cached metadata of the share, which is why, after performing persistUpdatedShare, If you keep acting on the initially fetched share, you will get this oplock error, you need to update your code to refetch the lastest CKShare in the persistUpdatedShare completion block.

I couldn't find any easy and clean solution with the current APIs, on top of it SWCollaborationView isn't calling properly its delegates, when it does call them (delegate, cloudSharingDelegate, cloudSharingControllerDelegate) which make it even more difficult.

I am now using SWColaborationView but with major bugs in Catalyst.

I ended up persisting the share using CKSystemSharingUIObserver blocks. https://developer.apple.com/documentation/cloudkit/cksystemsharinguiobserver

In the systemSharingUIDidSaveShareBlock I persist my share using persistUpdatedShare(_:in:completion:), and, in its completion block I dismiss the presented collaborationView, refetch the share and rebuild the SWCollaborationView and its itemProvider.

Hope this helps

Suber answered 4/1, 2023 at 13:45 Comment(4)
Thank you. Unfortunately Apple confirmed to me that SWColaborationView is not compatible with SwiftUI, and we should submit feedback. So I suppose your answer would work in UIKit only.Navicular
@Suber Do you maybe have some sample code for your final solution? I just can't get it to work and I'm not quite sure what exactly you are doing in the CKSystemSharingUIObserverChuringa
@Churinga sorry for the delay, as explained by bluefox this has been fixed now from iOS16.4 no the workaround is unneeded.Suber
@blueFox yes UIKit only for this workaroundSuber

© 2022 - 2024 — McMap. All rights reserved.