Post

Replies

Boosts

Views

Activity

Reply to Syncing Local Notifications using CloudKit
I implement this using (CloudCore for sync, and) an Observer object that reacts to changes in CoreData. Whether the change comes locally or synced, the notifications get queued/presented locally on all the users' devices. For example final class RemindersObserver: NSObject, NSFetchedResultsControllerDelegate {     private let persistentContainer: NSPersistentContainer     private let frc: NSFetchedResultsController<Reminder>              init(persistentContainer: NSPersistentContainer) {         self.persistentContainer = persistentContainer                  let moc = persistentContainer.newBackgroundContext()         moc.automaticallyMergesChangesFromParent = true         let fetchRequest: NSFetchRequest<Reminder> = Reminder.fetchRequest()         fetchRequest.sortDescriptors = [NSSortDescriptor(key: "moveBy", ascending: false)]                  frc = NSFetchedResultsController(fetchRequest: fetchRequest,                                          managedObjectContext: moc,                                          sectionNameKeyPath: nil,                                          cacheName: "RemindersObserver")                  super.init()                  frc.delegate = self         try? frc.performFetch()     }          func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {         let noteCenter = UNUserNotificationCenter.current()         noteCenter.removeAllPendingNotificationRequests()                  guard let reminders = frc.fetchedObjects else { return }                  for reminder in reminders {             guard let car = reminder.car, let uuidString = car.uuid else { continue }                          for countdown in Countdown.allCases {                 let content = UNMutableNotificationContent()                 content.categoryIdentifier = "reminderNotification"                 content.title = "Move the " + car.name!                 if countdown == .expired {                     content.subtitle = "Parking has expired!"                 } else {                     content.subtitle = "Parking expires in " + countdown.timeInterval().toString()                 }                 let notificationDate = reminder.moveBy! - countdown.timeInterval()                 let components: Set<Calendar.Component> = [.second, .minute, .hour, .day]                 let dateComponents = Calendar.current.dateComponents(components, from: notificationDate)                 let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)                 let request = UNNotificationRequest(identifier: uuidString + countdown.rawValue, content: content, trigger: trigger)                 noteCenter.add(request) { error in                     //                 }             }         }         WidgetCenter.shared.reloadTimelines(ofKind: Identifiers.carWidget)     } }
May ’21
Reply to CoreData, @FetchRequest, @ObservedObject and View Updating
fwiw, my experiences with DiffableDataSources and CoreData is that they are great at detecting additions and deletions (because the managed object IDs in the fetched results are different) but don't work for updates (because the ids stay the same, only the properties change). I thought I saw a very brief comment in a video about updating DiffableDataSources due to property changes, but damn if I can remember which video that was.
Jun ’21
Reply to CoreData, @FetchRequest, @ObservedObject and View Updating
also fwiw, in UIKit, I solve for this by observing the changed records of the FetchedRsultsController, and specifically reloading them in the diffable data source. …     func updateSnapshot() {         var diffableDataSourceSnapshot = NSDiffableDataSourceSnapshot<String, Car>()         frc.sections?.forEach { section in             diffableDataSourceSnapshot.appendSections([section.name])             diffableDataSourceSnapshot.appendItems(section.objects as! [Car], toSection: section.name)         }         diffableDataSourceSnapshot.reloadItems(changedCars)                  diffableDataSource?.apply(diffableDataSourceSnapshot, animatingDifferences: true)     }      } extension CarsListViewController: NSFetchedResultsControllerDelegate {          func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {                  if type == .update, let car = anObject as? Car {             changedCars.append(car)         }     }        func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {         self.updateSnapshot()         changedCars.removeAll()     } } Note this means I am not using the fancy new FRC delegate callbacks with snapshots, because they only have adds/deletes, no updates.
Jun ’21
Reply to CloudKit and CoreData synchronization
In CoreData, NSFetchRequest is used to perform a single fetch, whereas NSFetchedResultsController provides a continuous view of data matching your predicates. Furthermore, for SwiftUI, you should look at the property wrapper @FetchRequest, which in fact implements a NSFetchResultsController, and can be used instead of your ItemsModel class. Hope this helps.
Jun ’21
Reply to Why an invisible same cell is being updated when update is performing using NSFetchedResultsController and Diffable Data Source?
sigh… for reasons that boggle my mind, the snapshot returned by the FRC let snapshot = snapshotReference as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID> DOES NOT properly handle object updates, only inserts, deletions, and reorder. Its infuriating, frankly. So I have resorted to implementing my FRC delegate like this…     var changedCars: [Car] = []     func updateSnapshot() {         var diffableDataSourceSnapshot = NSDiffableDataSourceSnapshot<String, Car>()         frc.sections?.forEach { section in             diffableDataSourceSnapshot.appendSections([section.name])             diffableDataSourceSnapshot.appendItems(section.objects as! [Car], toSection: section.name)         }         diffableDataSourceSnapshot.reloadItems(changedCars)                  diffableDataSource?.apply(diffableDataSourceSnapshot, animatingDifferences: true)     }          func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {                  if type == .update, let car = anObject as? Car {             changedCars.append(car)         }     }          func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {         self.updateSnapshot()                  changedCars.removeAll()     }
Jun ’21
Reply to Is NSAsynchronousFetchRequest suitable for production app?
There are several issues here. CoreData is very specifically designed to manage memory efficiently, especially with large data sets. But you are correct that some queries can take a long time, and if performed on the main thread, can cause a hitch. When a query is performed, however, not all the data is loaded. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/FaultingandUniquing.html When configured with NSManageObjects, DiffableDataSource uses only the object IDs to detect changes, and the snapshots currently returned by FRC (as of iOS 15 beta 1) only include .inserts, .deletes, and .moves, but not .updates. If you want to handle updates, you have to specifically call snapshot.reload or snapshot.refresh for the changes objects. You can configure an NSFetchedResultsController to use a background NSManagedObjectContext. Any access to properties of the found objects must be done within a moc.perform { } block, or use the objectIDs to retrieve the objects on the main thread viewContext. Another idea is to re-create the behavior of FRCs by listening to CoreData notifications directly. See https://developer.apple.com/documentation/foundation/nsnotification/name/1506884-nsmanagedobjectcontextobjectsdid
Jun ’21
Reply to CloudKit public database subscription doesn't reach all users
It appears that subscribing to the public database is deprecated. CoreData's support for CloudKit public database does polling. See https://developer.apple.com/videos/play/wwdc2020/10650/ with a reference at the 7:30 mark, and a more-detailed discussion starting at 10:20. I would recommend the entire video, even if you're rolling your own CloudKit implementation, as it has lots of insights into how achieve sync without the tombstone magic in CKFetchRecordsZoneChangesOperation.
Jul ’21