7

WWDC 2019 was really packed with new stuff for iOS and the new data sources for TableViews and CollectionView which is UITableViewDiffableDataSource .

I have successfully integrate the above new data source with Core data , delete and insert new record working without any issue , unfortunately I have an issue with move items from section to another , the issue appears if I'm trying to move the last cell in the section .

Below is my code :

Table View Setup

private func setupTableView() {
       diffableDataSource = UITableViewDiffableDataSource<Int, Reminder>(tableView: remindersTableView) { (tableView, indexPath, reminder) -> UITableViewCell? in
           let cell = tableView.dequeueReusableCell(withIdentifier: "SYReminderCompactCell", for: indexPath) as! SYReminderCompactCell
        var reminderDateString = ""
        let reminderTitle = "\(reminder.emoji ?? "") \(reminder.title  ?? "")"

        if let date = reminder.date {// check if reminder has date or no , if yes check number of todos and if date in today
            let dateFormatter = SYDateFormatterManager.sharedManager.getDateFormaatter()
            dateFormatter.dateStyle = .none
            dateFormatter.timeStyle = .short
            reminderDateString = dateFormatter.string(from: date)
        }

            let toDosList = SYCoreDataManager.sharedManager.fetchAllToDosToOneReminder(reminder: reminder)
                 cell.indexPath = indexPath
                 cell.showMoreDelegate = self
                 cell.initializeToDosCompactView(toDoList: toDosList ?? [],reminderTitleText: reminderTitle,reminderDateText: reminderDateString)
                 cell.changeTextViewStyle(isChecked: reminder.isCompleted)

           return cell
       }

    setupSnapshot(animated: true)
   }

Create a NSDiffableDataSourceSnapshot with the table view data

private func setupSnapshot(animated: Bool) {
    diffableDataSourceSnapshot = NSDiffableDataSourceSnapshot<Int, Reminder>()

    for (i , section) in (fetchedResultsController.sections?.enumerated())! {
        diffableDataSourceSnapshot.appendSections([i])
        let items = section.objects
        diffableDataSourceSnapshot.appendItems(items as! [Reminder])
        diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: animated, completion: nil)
    }

}

NSFetchedResultsControllerDelegate for sections and rows

     func controller(_ controller: 
    NSFetchedResultsController<NSFetchRequestResult>, didChange 
    anObject: Any, at indexPath: IndexPath?, for type: 
    NSFetchedResultsChangeType, 
   newIndexPath: IndexPath?) {

    switch type {
    case .insert:
        if let indexPath = newIndexPath {
            let section = fetchedResultsController.sections![indexPath.section]
            self.diffableDataSourceSnapshot.appendItems(section.objects as! [Reminder], toSection: indexPath.section)
            self.diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: true)

        }
        break
    case .update:
        break
    case .delete:

        if let indexPath = indexPath {
            guard let item = self.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
            self.diffableDataSourceSnapshot.deleteItems([item])
            self.diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: true)
        }
        break
    case .move:
        if let indexPath = indexPath {
            guard let item = self.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
            self.diffableDataSourceSnapshot.appendSections([indexPath.section])
            self.diffableDataSourceSnapshot.deleteItems([item])
            self.diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: true)
        }
        if let newIndexPath = newIndexPath {
            let section = fetchedResultsController.sections![newIndexPath.section]

            // let items = fetchedResultsController.object(at: indexPath)
            print("snapppp" , diffableDataSourceSnapshot.sectionIdentifiers)
            let items = section.objects as! [Reminder]

            self.diffableDataSourceSnapshot.appendItems(items, toSection: newIndexPath.section)
            self.diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: true)
        }
        break
    }
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    switch type {
    case .insert:
        setupSnapshot(animated: false)
        break
    case .update:
        break
    case .delete:
        let section = self.diffableDataSourceSnapshot.sectionIdentifiers[sectionIndex]
        self.diffableDataSourceSnapshot.deleteSections([section])
                self.diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: true)
        //setupSnapshot(animated: false)

        break
    case .move:
        break
    }
}
ghadeer aqraa
  • 159
  • 2
  • 11
  • "issue appears" - what issue? Please be more precise in describing what happens. also you have this line `diffableDataSource?.apply(self.diffableDataSourceSnapshot, animatingDifferences: animated, completion: nil)`, it should not be inside for loop. Are you sure you want to apply the snapshot changes in the for loop? I'm pretty sure it's not noticable by the user so i'm not sure it makes sense. i would recommend to move it out of the for loop. – Tung Fam Nov 06 '19 at 20:05

1 Answers1

11
  • To work smoothly with Core Data the data source must be declared as

    UITableViewDiffableDataSource<String,NSManagedObjectID>
    
  • In setupTableView rename the closure parameter labels with

    (tableView, indexPath, objectID) -> UITableViewCell? in
    

    and get the reminder with

    let reminder = self.fetchedResultsController.object(at: indexPath)
    

    or

    let reminder = try! self.managedObjectContext.existingObject(with: objectID) as! Reminder
    
  • Then replace the entire methods

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, 
         didChange anObject: Any, 
         at indexPath: IndexPath?, 
         for type: NSFetchedResultsChangeType, 
         newIndexPath: IndexPath?) { ... }
    

    and

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, 
         didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, 
         for type: NSFetchedResultsChangeType) { ... }
    

    just with

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {       
        self.diffableDataSource.apply(snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>, animatingDifferences: true)
    }
    
  • Delete also the method setupSnapshot, it is not needed. After calling performFetch and on any change in the managed object context the framework creates the snapshot properly and calls the delegate method.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Good answer I would only add that animatingDifferences should be true or false depending on if it is during the initial performFetch. – malhal May 27 '20 at 18:58
  • May I know do you have any reference source, to discuss why NSManagedObjectID should be used instead of NSManagedObject, in DiffableDataSource? Thanks. – Cheok Yan Cheng Mar 23 '21 at 14:08
  • @CheokYanCheng The related WWDC 2019 Videos. – vadian Mar 23 '21 at 14:47
  • Thanks. The closest I can get is https://developer.apple.com/videos/play/wwdc2019/230/ (Making Apps with Core Data). But it doesn't specifically discuss with NSManagedObjectID is used. If you are using NSManagedObjectID, isn't if there is content change, you are not able to detect such? As, 2 objects with different content can have same NSManagedObjectID. – Cheok Yan Cheng Mar 23 '21 at 15:22
  • The `NSManagedObjectID` is just a quick way to identify a specific record. The difference of the two snapshots is determined anyway – vadian Mar 23 '21 at 15:30