Cannot share CloudKit CKShare record - ios

I'm trying to implement cloud kit sharing in my application, however, whenever I try to share an item using a UICloudSharingController I'm getting a consistent error:
I am presented with the initial share popover for adding people, and then when I select one of the options on how I'd like to send the invitation (i.e: by mail), the UICloudSharingControllerDelegate returns calling:
func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error)
And throws the error:
CKError 0x170245d60: "Invalid Arguments" (12); "An added share is being saved without its rootRecord (CKRecordID: 0x1700343e0; recordName=C9FA0E96-3461-4C9E-AB99-3B342A37A07A, zoneID=PrivateDatabase:__defaultOwner_)"
I've already created a custom zone in the private cloud database for the user whose zoneId is "PrivateDatabase". I've created an object and successfully saved it to iCloud and it is linked to the custom zone I previously created. The code I am using to present the UICloudSharingController is as follows:
let object = // A core data representation of a CKRecord //
let share = CKShare(rootRecord: object.record) //record is a CKRecord that is stored with the core data object
share[CKShareTitleKey] = object.name as? CKRecordValue
share[CKShareThumbnailImageDataKey] = UIImagePNGRepresentation(object.categoryKey.icon()) as? CKRecordValue
share[CKShareTypeKey] = "reverse.domain" as CKRecordValue
share.publicPermission = .readOnly
let sharingController = UICloudSharingController(share: share, container: self.container)
sharingController.delegate = self
sharingController.availablePermissions = [.allowPrivate, .allowReadOnly]
sharingController.popoverPresentationController?.sourceView = sourceView
controller.present(sharingController, animated: true, completion: nil)
What am I missing here?

You are using the wrong initializer for an instance of UICloudSharingController.
There are two different initializers for two different use cases.
If an item is shared the first time (not already shared) you have to create a new instance of CKShare with a rootRecord (as you did in your code) but then you have to initialize your UICloudSharingController with this Initializer init(preparationHandler:)
If an item is already in a sharing process, then you have to fetch the existing share from iCloud and initialize your UICloudSharingController with this Initializer init(share:container:)
So, the error in your case is, that you create a new instance of CKShare but then use the wrong initializer.
more from Apple

did you check in iCloud, whether the share has the correct link to the root record in the correct zone etc., and/or the rootRecord is existing...

Related

Core Data sharing with CloudKit: Unable to Accept Share

I had my app working with Core Data, then CloudKit to sync between devices and now I'd like to share data between users. I watched both Build apps that share data through CloudKit and Core Data and What's new in CloudKit WWDC21 and thought that I got the concepts down. CloudKit uses zone sharing and CKShares to handle sharing and Core Data attaches to this implementation natively in iOS15.
I setup my Core Data stack as such:
/// Configure private store
guard let privateStoreDescription: NSPersistentStoreDescription = persistentContainer.persistentStoreDescriptions.first else {
Logger.model.error("Unable to get private Core Data persistent store description")
return
}
privateStoreDescription.url = inMemory ? URL(fileURLWithPath: "/dev/null") : privateStoreDescription.url?.appendingPathComponent("\(containerIdentifier).private.sqlite")
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
persistentContainer.persistentStoreDescriptions.append(privateStoreDescription)
/// Create shared store
let sharedStoreDescription: NSPersistentStoreDescription = privateStoreDescription.copy() as! NSPersistentStoreDescription
sharedStoreDescription.url = sharedStoreDescription.url?.appendingPathComponent("\(containerIdentifier).shared.sqlite")
let sharedStoreOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier)
sharedStoreOptions.databaseScope = .shared
sharedStoreDescription.cloudKitContainerOptions = sharedStoreOptions
persistentContainer.persistentStoreDescriptions.append(sharedStoreDescription)
persistentContainer.loadPersistentStores(...)
Implemented the SceneDelegate user acceptance:
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
let container = PersistenceController.shared.persistentContainer
let sharedStore = container.persistentStoreCoordinator.persistentStores.first!
container.acceptShareInvitations(from: [cloudKitShareMetadata], into: sharedStore, completion: nil) //TODO: Log completion
}
However after sharing the NSObject as such in my UI using UICloudSharingController as seen below:
let object: NSObject = // Get Object from view context
let container = PersistenceController.shared.persistentContainer
let cloudSharingController = UICloudSharingController { (controller, completion: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
container.share([object], to: nil) { objectIDs, share, container, error in
completion(share, container, error)
Logger.viewModel.debug("Shared \(household.getName())")
}
}
cloudSharingController.delegate = self
self.present(cloudSharingController, animated: true) {}
My SceneDelegate method is never called and I get the following alert when I press the invite from the messages app. I'm not quite sure what is wrong in this case as on the CloudKit developer console I see the object in a private database with the zone of com.apple.coredata.cloudkit.share.[UUID]. I have not released the app yet so I'm not sure where it is getting version information from as both apps were launched from the Xcode debugger(same version & build). Additionally I was unable to find reference this alert on other questions so any advice, suggestions, or help is welcome as I have been stuck on this for a few evenings. Please let me know if there is more information that could shine light on this problem.
I had the same problem and it was solved when I added the CKSharingSupported key with a Bool value of true in the Info.plist
After that I was able to share with no problem.

Which to use, addSnapshotListener() or getDocuments()

I'm a little bit confused about addSnapshotListener and getDocuments. As I read in the firebase docs, getDocuments() is retrieving data once and addSnapshotListener is retrieving in real-time.
What I want to ask.
If I'm using getDocuments, and im changing some documents in the Firestore , it will not make the change in the app ? But if im using addSnapshotListener it will ?
I'm making an delivery app, which is the best to use to store pictures of food , descriptions etc.
This is what im using to retrieve labels and pictures from my app :
db.collection("labels").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let newEntry = Labels(
firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String,
photoKey: data["photoKey"] as! String
)
self.labels
.append(newEntry)
}
}
DispatchQueue.main.async {
self.tableViewTest.reloadData()
}
getDocuments will return results one time, with the current Firestore data.
addSnapshotListener will return an initial result set (same as getDocuments) and get called any time that data changes.
If your data is modified in Firestore and you've used getDocuments, your app will not be notified of those changes. For example, in your delivery app, perhaps the item goes out-of-stock while the user is using it. Or, the price gets changed, the user is logged in from another device, etc -- many possibilities for why the data might change. By using a snapshot listener, you'd get notified if any of these changes happen.
However, if you're relatively confident you don't need updates to the data (like getting a user's address from the database, for example), you could opt to just use getDocuments.

Converting local Realm to synced Realm in the middle of app life cycle (in Swift)

My app will have a paid feature called multi-devices sync. I would like to implement the feature with Realm Cloud - Query Based Sync.
I know how to convert local Realm to synced Realm thanks to
this thread.
But this is based on the scenario that users sync their Realm from the app start - before opening their non-synced local realm. That doesn’t work for me because my users will start sync when they paid for it.
Therefore, I have to convert their local Realm in the middle of app life cycle and the local Realm is already opened by that time.
My issue comes in here. When I try to convert local realm to synced realm, app crashes with this message:
Realm at path ‘…’ already opened with different read permissions.
I tried to find a way to close local Realm before converting it, but Realm cocoa does not allow me to close a Realm programmatically.
Here’s my code converting local Realm to synced Realm.
func copyLocalRealmToSyncedRealm(user: RLMSyncUser) {
let localConfig = RLMRealmConfiguration()
localConfig.fileURL = Realm.Configuration.defaultConfiguration.fileURL
localConfig.dynamic = true
localConfig.readOnly = true
// crashes here
let localRealm = try! RLMRealm(configuration: localConfig)
let syncConfig = RLMRealmConfiguration()
syncConfig.syncConfiguration = RLMSyncConfiguration(user: user,
realmURL: realmURL,
isPartial: true,
urlPrefix: nil,
stopPolicy: .liveIndefinitely,
enableSSLValidation: true,
certificatePath: nil)
syncConfig.customSchema = localRealm.schema
let syncRealm = try! RLMRealm(configuration: syncConfig)
syncRealm.schema = syncConfig.customSchema!
try! syncRealm.transaction {
let objectSchema = syncConfig.customSchema!.objectSchema
for schema in objectSchema {
let allObjects = localRealm.allObjects(schema.className)
for i in 0..<allObjects.count {
let object = allObjects[i]
RLMCreateObjectInRealmWithValue(syncRealm, schema.className, object, true)
}
}
}
}
Any help will be appreciated.
Thanks.
I made a copy of the local realm file and opened the copy with RLMRealmConfiguration. Afterwards, just delete both files. It is not the best solution, but it works

Pass data to Call Directory Extension

I cannot pass data call directory extension from main app
I've created one App group and turned it on in App capabilities for both targets (main app and extension)
I pass data with NSUser
-Create Data in main app
var userDefaults = UserDefaults(suiteName: "group.test.callapp")
userDefaults?.set("mynumber", forKey: "mykey")
-Retrieve Data in extension
var baseDescription = "test"
let newUserDefaults = UserDefaults(suiteName: "group.test.callapp")
if let testUserId = newUserDefaults?.object(forKey: "mykey") as? String
{
baseDescription = testUserId
}
When i do it in only main app - everything is ok, but when i do it in extension (i do it in 'addIdentificationPhoneNumbers' function) - it doesn't work, baseDescripton doesn't change
Your solutions shoud work, the only problem I can see is that you don't have App Groups enabled (with your "group.test.callapp" group) both for your application and the extension. Check Target->Capabilities if this is enabled for both. Another caveat is to pass the phone number in the international format (with country code).

how to keep track of reminder which is saved in iphone or ipad?

I am working on this application that will save reminder. but i do not know how to keep track of saved reminder.
In the Case of event we have eventIdentifier but for Reminder i am getting nothing like that.
I am using realm to save data and using REST API.while fetching data from server i am creating objects and at that time i am also creating realm objects and creating reminder. but i don't know how to check that reminder is added for that object or not.
Two examples. One for RealmObject, one for ReminderObject:
import Foundation
class RealmObject: NSObject {}
class ReminderObject: NSObject {}
let realmObject = RealmObject()
let reminderObject = ReminderObject()
// To save objects...
UserDefaults.standard.set(realmObject, forKey: "realmObject")
UserDefaults.standard.set(reminderObject, forKey: "reminderObject")
// To retrieve objects...
let retrievedRealm = UserDefaults.standard.object(forKey: "realmObject") as? RealmObject
let retrievedReminder = UserDefaults.standard.object(forKey: "reminderObject") as? ReminderObject

Resources