Build apps that share data through CloudKit and Core Data

RSS for tag

Discuss the WWDC21 session Build apps that share data through CloudKit and Core Data.

View Session

Posts under wwdc21-10015 tag

29 Posts
Sort by:
Post not yet marked as solved
1 Replies
212 Views
I'm finding this issue after making sure I create an entity with the correspondent attribute. This issue comes from a relation between 2 entities: GroupData and TopicData. Their relations are like shown in the screenshots. Then, I get the context from a function like the next one. Every time I need to use the context, I call this function. import CoreData class DataController: ObservableObject {     let container = NSPersistentContainer(name: "vocabul-R")     init() {         container.loadPersistentStores { description, error in                 if let error = error {                     print("CoreData failed to load: \(error.localizedDescription)")                 }         }         container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump         container.viewContext.retainsRegisteredObjects = true     } } I first create the objects from data I get from csv files. By debugging I've made sure that the objects created have the correspondent values for each attribute and the correct objects in relations.     guard let filepath = Bundle.main.path(forResource: data_file, ofType: "csv") else {         return     }     let dataController = DataController()     let context = dataController.container.viewContext     do {         try context.save()         }     catch {     }     var data = ""     do {         data = try String(contentsOfFile: filepath)     } catch {         return     }     var rows = data.components(separatedBy: "\n")     rows.remove(at: 0)     for row in rows {         let columns = row.components(separatedBy: ",")         let name = columns[0]         let activation_level = (columns[1] as NSString).integerValue         let activated: Bool = false         let last_opened_group: Int16 = 0         let current_level: Int16 = 0         let topic = TopicData(context: context)         topic.name = name         topic.activated = activated         topic.activationLevel = Int16(activation_level)         topic.last_opened_group = last_opened_group         topic.current_level = current_level         do {             try context.save()             }         catch {             print("Context not saved")         }     } } func parseCSV_groups (data_file: String, topic_name: String) {     guard let filepath = Bundle.main.path(forResource: data_file, ofType: "csv") else {         return     }     var data = ""     do {         data = try String(contentsOfFile: filepath)     } catch {         return     }     var rows = data.components(separatedBy: "\n")     rows.remove(at: 0)     let dataController = DataController()     let context = dataController.container.viewContext     let topic = TopicData.getTopicFromName(name: topic_name, context: context)     let train_list = Train_list(context: context)     let test_list = Test_list(context: context)     train_list.train_list = "train_list"     test_list.test_list = "test_list"     for row in rows {         let columns = row.components(separatedBy: ",")         let index_group = (columns[0] as NSString).integerValue         let mean_level = (columns[1] as NSString).integerValue         let cleared_words: Int16 = 0         let group = GroupData(context: context)         group.index_group = Int16(index_group)         group.mean_level = Int16(mean_level)         group.cleared_words = cleared_words         //group.topic = topic         topic.addToGroup(group)         do {             try context.save()             }         catch {             print("Context not saved")         }     } } I have created objects from both entities with relations. I have other functions with relations between these and other entities seem to work fine. But now I need to get all the groups that one TopicData object has. For that, I first get a topic from its name.         let fetchRequest = NSFetchRequest<TopicData>(entityName: "TopicData")         fetchRequest.predicate = NSPredicate(format: "name = %@", name)         let topics = (try? context.fetch(fetchRequest)) ?? []         let topic = topics.first ?? TopicData()         return topic     } This seems to work fine as I can print the topic name and the object type (which is TopicData). But then I try to get the groups that it has. I've also tried to get the groups with a fetchRequest     /fetchRequest.predicate = NSPredicate(format: "topic = %@", topic)     let groups = (try? context.fetch(fetchRequest)) ?? [] And when I try to get one of the attributes (activated) of one of these topics, I also get an error. I thought this could be happening because every time I use a topic to update it and save it on context again, a new topic is created which may not be related to GroupData. But I added a constraint in TopicData for name and I still get the same error. Another thing I thought is that relations could be disappearing, since I saw some people had this problem and the issue was that. But I added the retainsRegisteredObjects line in DataController and it didn't work. The full error I get is: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TopicData group]: unrecognized selector sent to instance 0x600003812bc0' terminating with uncaught exception of type NSException I think that is all about the problem. Any idea of what could be happening? I think I added everything relevant for the problem, but let me know if there is some other code I should add. Thank you!
Posted
by
Post not yet marked as solved
1 Replies
151 Views
I'm getting this error because it seems the database forgets objects relations. I always use the same context (with different instances on each function), so I think I can't do anything with mutableSetValue. Also, I've tried to set retainsRegisteredObjects, but it doesn't seem to improve anything. An option I was thinking about is always using the same context and send it from one function to the next one, but it doesn't feel like that's an optimal solution. And I'm not sure if relations would stay after I closed the app, which is something I need. Any ideas of how to solve this issue?
Posted
by
Post not yet marked as solved
0 Replies
119 Views
I’m new to trying to modify share records and not having any luck with it. Somehow, I’ve made it such that every new coredata record I create is already listed as shared and accepted by another user (my other sandbox). This user never got a link nor has the record. I’d like to correct that issue. But hacking away at it, I can’t find a solution or even know what caused this. I assume I need to modify the record of the privateCloudDatabase?
Posted
by
Post marked as solved
1 Replies
214 Views
I have been stuck on this for some time now, I built a tableView App with the ability to delete a row. I am using with CoreData and iCloud. When I use "editActionsForRowAt" it deletes from the view and does not re-appear when I close and run the App again, the problem is as you know it is deprecated in iOS 13. 1.) This works great but is deprecated in iOS 13 override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { //Social Sharing Button let shareAction = UITableViewRowAction(style: UITableViewRowAction.Style.default, title: "share", handler: { (action, indexPath) -> Void in _ = "Just checking in at " + self.inspections[indexPath.row].stateID! }) // Delete Button let deleteAction = UITableViewRowAction(style: UITableViewRowAction.Style.default, title: "Delete",handler: { (action, indexPath) -> Void in if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) { let context = appDelegate.persistentContainer.viewContext let inspectionsToDelete = self.fetchResultController.object(at: indexPath) context.delete(inspectionsToDelete) appDelegate.saveContext() } }) deleteAction.backgroundColor = UIColor(red: 237.0/255.0, green: 66.0/255.0, blue: 106.0/255.0, alpha: 1.0) shareAction.backgroundColor = UIColor(red: 63.0/255.0, green: 212.0/255.0, blue: 78.0/255.0, alpha: 1.0) return [deleteAction, shareAction] } 2.) So I ran with "trailingSwipeActionsConfigurationForRowAt" This should work but it will not swipe to delete the row. func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationsForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let deleteAction = UIContextualAction(style: .normal, title: "Delete", handler: {(action, view, success) in if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) { let context = appDelegate.persistentContainer.viewContext let inspectionsToDelete = self.fetchResultController.object(at: indexPath) context.delete(inspectionsToDelete) appDelegate.saveContext() } }) return UISwipeActionsConfiguration(actions: [deleteAction]) } 3.) This deletes without 1.) or 2.) but repopulates the row when I reopen the App override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { // delete the row from the data source inspections.remove(at: indexPath.row) } tableView.deleteRows(at: [indexPath], with: .fade) }
Posted
by
Post not yet marked as solved
0 Replies
223 Views
I'm working on an app that uses NSPersistentCloudKitContainer to handle CloudKit sharing. Against all odds I've gotten the sharing to work, but now I'm seeing errors on startup that look very much like some kind of background loop trying to merge changes from multiple users and failing. In a more traditional CloudKit installation not backed on NSPersistentCloudKitContainer this feels like a case where I'd have to provide some code to handle the merge. In the brave new world I can't seem to find anyway to affect this Mirroring Delegate. It starts when I initialize the NSPersistentCloudKitContainer and produces the error below (as well as a long stream of similar errors). Any ideas? error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _exportFinishedWithResult:exporter:](1347): <PFCloudKitExporter: 0x282d2ead0>: Export failed with error: <CKError 0x280079470: "Partial Failure" (2/1011); "Failed to modify some records"; partial errors: { cloudkit.zoneshare:(Pacts:__defaultOwner__) = <CKError 0x280079680: "Server Record Changed" (14/1022); "Participants conflict while trying to update share from the server. Participants: === Client: (     "<CKShareParticipant: 0x100cebb70; participantID=..., isCurrentUser=true, role=owner, permission=readWrite, acceptanceStatus=Accepted, identity=<CKUserIdentity: 0x100c4cae0; userID=__defaultOwner__:(_defaultZone:__defaultOwner__), nameComponents=, cached=false, publicKeyVersion=2>, hasProtectionInfo=true, invitationTokenStatus=Healthy, isAnonymousInvitedParticipant=false>" ) === Server: (     "<CKShareParticipant: 0x100c26db0; participantID=..., isCurrentUser=true, role=owner, permission=readWrite, acceptanceStatus=Accepted, identity=<CKUserIdentity: 0x100c13b60; userID=__defaultOwner__:(_defaultZone:__defaultOwner__), nameComponents=..., lookupInfo=<CKUserIdentityLookupInfo: 0x100c13c00; email=...>, cached=false, publicKeyVersion=2>, hasProtectionInfo=true, invitationTokenStatus=Healthy, isAnonymousInvitedParticipant=false>",     "<CKShareParticipant: 0x100c25960; participantID=..., isCurrentUser=false, role=user, permission=readWrite, acceptanceStatus=Accepted, identity=<CKUserIdentity: 0x100c259f0; userID=_6cd4c7c8091c946d4b8e704efbfc0bc4:(_defaultZone:__defaultOwner__), nameComponents=..., lookupInfo=<CKUserIdentityLookupInfo: 0x100c68d10; email=...>, cached=false, publicKeyVersion=2>, hasProtectionInfo=true, invitationTokenStatus=Healthy, isAnonymousInvitedParticipant=false>" )"> }>
Posted
by
Post marked as solved
1 Replies
276 Views
Hi, I always though @FetchedResults should automatically update, but it seems this is not always true, or do I have to trigger updates myself? I am using Core Data with CloudKit sync. When I start my app, nothing is visually updating while it'S doing the first sync. Only after that view is reloaded it shows the synced data. Any idea what I have to do automatically get updates when the data is syncing the background. All the best Christoph
Posted
by
Post not yet marked as solved
0 Replies
180 Views
Hi, I'm building and when it start the app install in the simulator, this error happens Domain: NSPOSIXErrorDomain Code: 53 Failure Reason: Software caused connection abort -- System Information macOS Version 10.15.7 (Build 19H1519) Xcode 12.4 (17801) (Build 12D4e) Timestamp: 2022-02-24T20:05:09-03:00
Posted
by
Post not yet marked as solved
0 Replies
232 Views
have two quote dictionaries I'm trying to append after an in-app purchase. I have tried several different methods to append the dictionaries together but I'm still getting an error "No exact matches in call to instance method 'append'" I have established variables for each array to then append the array within the struct. Any thoughts? Is there a better method I should use to add the quotes from the array called QuoteDetails2 to the initial array QuoteDetails? Here is the code I'm trying to correct: var topQuotes = [QuoteDetails]() var additionalQuotes = [QuoteDetails2]() public struct QuoteProvider { static func all() -> [QuoteDetails] { return [ QuoteDetails( id: “1”, texts: “High school is fun”, authors: “SM” ), QuoteDetails( id: “2”, texts: “Middle School is fun”, authors: "A. Philip Randolph" ), QuoteDetails( id: “3”, texts: “The playground is fun”, authors: "Booker T. Washington" ), QuoteDetails( id: “4”, texts: "Hold on to your dreams of a better life and stay committed to striving to realize it.", authors: “KJ” ) ] } static func all2() -> [QuoteDetails2] { return [ QuoteDetails2( id: "1", texts: "The cat ran fast”, authors: " ME” ), QuoteDetails2( id: "2", texts: “The dog ran fast.”, authors: " ME” ), QuoteDetails2( id: "3", texts: "End life problems”, authors: “ME” ) ] } func showPremiumQuotes() { if UserDefaults.standard.bool(forKey: "premiumQuotes") == true { topQuotes.append(contentsOf: additionalQuotes) } } /// - Returns: A random item. static func random() -> QuoteDetails { let allQuotes = QuoteProvider.all() let randomIndex = Int.random(in: 0..<allQuotes.count) return allQuotes[randomIndex] } }
Posted
by
Post not yet marked as solved
0 Replies
415 Views
Hi, I wanted to offer the UI for editing and deleting a share. So for using UICloudSharingController(share: , container: ) I implemented the delegate to call "persistUpdatedShare" in "cloudSharingControllerDidSaveShare" func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) { if let share = csc.share,   let privatePersistentStore = PersistenceController.shared.privatePersistentStore { PersistenceController.shared.container.persistUpdatedShare(share, in: privatePersistentStore) { (share, error) in Thread.performOnMain { if let error = error { PersistenceController.shared.storeWarning = error } } } } } and to call "purgeObjectsAndRecordsInZone" in "cloudSharingControllerDidStopSharing" func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) { let puzzleToShare = puzzleToShare if let share = csc.share,   let privatePersistentStore = PersistenceController.shared.privatePersistentStore { PersistenceController.shared.container.purgeObjectsAndRecordsInZone(with: share.recordID.zoneID, in: privatePersistentStore) { (zoneID, error) in Thread.performOnMain { if let error = error { PersistenceController.shared.storeWarning = error } else { puzzleToShare.makePrivateDuplicate() try? puzzleToShare.puzzle.managedObjectContext?.save() } } } } } I got this implementation from a TSI, but it makes no sense to me to delete and duplicate my objects rather than just keeping them and only deleting the share. Question: How can I cleanly discontinue a share, but keep my data. Duplicating my objects seems such a weird approach. But without this implementation it does not really work. (I get strange behaviors and crashes) All the best Christoph
Posted
by
Post not yet marked as solved
1 Replies
358 Views
I am trying to add CloudKit sharing to my app using the new iOS 15 share method https://developer.apple.com/documentation/coredata/nspersistentcloudkitcontainer/3746834-share In the app there are 3 core data entities (Folder, Item, Comment) with the following relationships: A Folder contains many Items An Item has many Comments I want to use CloudKit to share just the Item entity, not any of its relationships. Is this possible to do with the share(_:to:completion:) method? Currently, when I pass an Item to the share method it also includes the Folder and Comments in the CKShare. How do I prevent this?
Posted
by
Post not yet marked as solved
0 Replies
306 Views
I am running the WWDC21 sample application "CoreDataCloudkitDemo" with two different Apple IDs to test the Cloudkit sharing feature. I use two simulators and one real device, where the latter has the same ID as one of the simulators. While creating invites via "Copy link" works on all three installations, accepting invites only works on the real device. Trying to open an invite (by copying the link in the one and pasting it into Safari in the other) always results in a web page stating that "iCloud has stopped responding" and "Unable to find applicationIdentifier for the containerId = [my ID] and fileExtension = undefined". Is this a simulator bug in Xcode 13.2 or is there anything I can do about it? Other iCloud features (including login via Safari to icloud.com) work in both simulators. Is there a way to open invite URLs in a simulator (as there is no functioning iMessage or Mail app) other than Safari's URL input field? Doing so does not work in the real device either, but clicking it in e.g. iMessage works. Edit: Pasting the link into a ToDo item and opening it from there works even in the simulator - so is it just a Safari issue?
Posted
by
LPG
Post not yet marked as solved
2 Replies
735 Views
Dear: Use core data to store super long strings. After storage, the strings are incomplete. Xcode tips: CoreData: debug: PostSaveMaintenance: fileSize 30409752 greater than prune threshold CoreData: annotation: PostSaveMaintenance: wal_checkpoint(TRUNCATE)  help me?
Posted
by
Post not yet marked as solved
0 Replies
375 Views
Now, when using NSPersistentCloudKitContainer.share(_ managedObjects: [NSManagedObject], to share: CKShare?), A deep traversal will be performed among the objects and any related objects will also be shared. For example, if there are 100 posts with the same tag, I shared one of them, and the remaining 99 will be shared at the same time. Is there any way to control whether the relationship should be shared?
Posted
by
Post not yet marked as solved
0 Replies
335 Views
Hello everyone, in the App I'm building I'd like to share an entire list of items (records) by just sharing the list itself (parent record or RecordZone). Meanwhile, I'd like to continue using the NSPersistentCloudKitContainer and therefore CoreData sync. Is it possible, with the new features Apple introduced (Sharing RecordZone and Sharing in Cloud & Local Storage), to do what I described, and combine both functions. Thank you, Carlos Steiner
Posted
by
Post not yet marked as solved
0 Replies
340 Views
I have an app for client management that stores data in Core Data with an NSPersistentCloudKitContainer. Each manager have their clients in the Core Data Private Database, and each client could have associated files (images, documents, etc) that are stored in app's own folder structure in iCloud Drive. Eventually, a manager can decide if to share a client with another manager, in order to have a shared managed client (readable, or writable+readable by others managers which share the same client). This is done by moving the Client from the Private Database to the Shared Database following the Apple's guidelines: https://developer.apple.com/videos/play/wwdc2021/10015/ This is the structure: The problem is: the database record is shared correctly, but the iCloud Drive Files are not shared (obviously). My goal is to get working the iCloud Drive Client Files (every client has a single Folder) with the minimum effort from the user. I cannot get working the iCloud Drive Sharing from my app, only the Core Data Sharing, so (in development environment) I have to share the Client Core Data Info from the App sheet, and then, go to Finder and share the Client's folder and send the link, so it would be a bit confusing for my users. Any approach for get working iCloud Drive File Sharing from the (inside) my app? Generate a link and automatically send to the other user. Or, better, an approach to get working this sharing with a single action from the user (only share once, and it sends the core data info and icloud drive file sharing).
Posted
by
Post not yet marked as solved
0 Replies
343 Views
I create a tool XLLoger, that can redirect stdout to the textView, It works well on iOS14 and below, but on iOS15, when the app running on Xcode connecting the device or simulator, the code can get the logs, if disconnected xcode, the app can't get print log, the NSLog can be get. I changed a variety of methods to redirect logs, all of which are the same phenomenon. Anything wrong with my code?
Posted
by
Post not yet marked as solved
8 Replies
2.2k Views
I have been excited to add NSPersistentCloudKitContainer's share functionality to my app but I've noted a few thing I suspect are bugs: -When using share() on a NSManagedObject the share record is set up in a new zone. However, the root NSManagedObject's record is not being updated with the relationship linkage to the shared record. -Secondly, when you revoke a share, the cloudkit.share record is removed from iCloud, but not in the local data stores. This makes the fetchShares() method ineffective for detecting a missing cloudkit.share record. In order to re-share the root object the developer must call out to iCloud directly using the old methods to be sure if the share exists or not. I am using the code from Apple's 'Synchronizing a Local Store to the Cloud' sample. It would be nice if they added support for revoking shares into this sample and addressed these issues.
Posted
by
Post not yet marked as solved
1 Replies
531 Views
I am trying to use the new API provided by WWDC21 to create Core Data databases that can be shared. When I do the sharing for an NSManageObject, I tried to experiment with 2 methods. 1: Using UICloudSharingController's UICloudSharingController(preparationHandler: (UICloudSharingController, @escaping (CKShare?, CKContainer?, Error?) -> Void) -> Void) method. Currently this method works and the participants can get all the data after accepting the invitation. 2:Use NSPersistentContainer's share(_ managedObjects: [NSManagedObject], to share: CKShare?) async throws -> (Set<NSManagedObjectID>, CKShare, CKContainer) method. First get ckshare, then call UICloudSharingController(share:, container: ) With this approach, after accepting the invitation, the participant only gets the data generated after the creation of the ckshare, and the data before the ckshare is never fetched I'm not sure if it's an error in the token generated by the share method of the NSPersistentContainer or something else. Has anyone encountered a similar situation?
Posted
by
Post marked as solved
1 Replies
788 Views
I was trying to have a secondary collection view with circle images above (y axis) the functioning collection view but its crashing and throwing this error Error code: could not dequeue a view of kind: UICollectionElementKindCell with identifier CircleCollectionViewCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard ViewController code: // //  ViewController.swift //  Main_app // //  Created by Kushagr Agarwal on 23/08/21. // import UIKit class ViewController: UIViewController {     @IBOutlet var collectionView: UICollectionView!          @IBOutlet var topCollectionView: UICollectionView!          @IBOutlet weak var Leading: NSLayoutConstraint!          @IBOutlet weak var Trailing: NSLayoutConstraint!          @IBOutlet private weak var Menubutton: UIButton!     var menuOut = false               override func viewDidLoad() {         super.viewDidLoad()         // Do any additional setup after loading the view.         //self.navigationItem.titleView = UIImageView(image: UIImage(named: "Logo"))                  collectionView.register(MiddleCollectionViewCell.nib(), forCellWithReuseIdentifier: MiddleCollectionViewCell.identifier)                  collectionView.delegate=self         collectionView.dataSource=self                  topCollectionView.register(CircleCollectionViewCell.self, forCellWithReuseIdentifier: CircleCollectionViewCell.identifier)         topCollectionView.delegate=self         topCollectionView.dataSource=self //       Menu //       start                  let destructiveAction = UIAction(title: "Delete",image: UIImage(systemName: "nosign") , attributes: .destructive) { (_) in             print("Delete")         }                  let menu = UIMenu(title: "", children: [             UIAction (title: "Add New", image: UIImage(systemName: "plus.circle"), handler: { _ in  }),             UIAction (title: "Manage", image: UIImage(systemName: "hammer"), handler: { _ in  }),             destructiveAction         ])         self.Menubutton.menu = menu //    Menu end     } // Menu animation     @IBAction func MenuTap(_ sender: Any) {         if menuOut == false {             Leading.constant = -150             Trailing.constant = 150             menuOut = true         }         else{             Leading.constant = 0             Trailing.constant = 0             menuOut = false         }         UIView.animate(withDuration: 0.2, delay: 0.0 , options: .curveEaseIn,animations: {             self.view.layoutIfNeeded()         }){(animationComplete) in             print("Animation Completed")         }     } // Menu animation ends } // used for upper collection view extension ViewController: UICollectionViewDelegate {          func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {         collectionView.deselectItem(at: indexPath, animated: true )         print("btn tapped")              }           } extension ViewController: UICollectionViewDataSource {     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {         return 12     }     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MiddleCollectionViewCell.identifier, for: indexPath) as! MiddleCollectionViewCell         let topcell = collectionView.dequeueReusableCell(withReuseIdentifier: CircleCollectionViewCell.identifier, for: indexPath) as! CircleCollectionViewCell         topcell.configure(with: "")                  cell.configure(with: UIImage(named: "four")!)         return cell; topcell     } } //extension ViewController: UICollectionViewDelegateFlowLayout { // //}
Posted
by