12 Replies
      Latest reply on Oct 21, 2019 9:11 AM by mattn
      anwill999 Level 1 Level 1 (10 points)

        Using the iOS 13 beta SDK, I'm trying to drive updates to a UICollectionView using a NSFetchedResultsController and the new NSDiffableDataSource APIs as outlined in session 230 - 'Building Apps with Core Data'.

         

        Using the NSFetchedResultsController delegate method 'didChangeContent withSnapshot', I'm trying to for cast the NSDiffableDataSourceSnapshotReference to a NSDiffableDataSourceSnapshot as shown in the slides. However, I'm running into a compile error when trying to do so - "Generic parameter 'SectionIdentifierType' could not be inferred in cast to 'NSDiffableDataSourceSnapshot'".

         

        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference<NSManagedObjectID, NSString>) {
                
                dataSource.apply(snapshot as! NSDiffableDataSourceSnapshot, animatingDifferences: true)
            }
        

         

        Is Anyone else seeing this error? Any thoughts on how I can get past it?

         

        I've also filed a feedback report - FB6135082.

         

        Thanks,

         

        Andrew

        • Re: Unable to cast NSDiffableDataSourceSnapshotReference
          IngmarStein Level 1 Level 1 (15 points)

          This doesn't look ready, yet. The example is clearly wrong, as the cast operates on unrelated types and always fails when you add the missing generic paramaters, given the following class declarations in beta 1:

           

          open class NSDiffableDataSourceSnapshotReference<sectionidentifiertype, itemidentifiertype=""> : NSObject, NSCopying where SectionIdentifierType : AnyObject, ItemIdentifierType : AnyObject {
          public class NSDiffableDataSourceSnapshot<sectionidentifiertype, itemidentifiertype=""> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
            • Re: Unable to cast NSDiffableDataSourceSnapshotReference
              IngmarStein Level 1 Level 1 (15 points)

              Also, if you try to use the NSDiffableDataSourceSnapshotReference that is passed to your delegate in controller(_, didChangeContentWith:), you get all sorts of type errors which seem to indicate a problem with how this type is imported into Swift:

               

              (lldb) po snapshot.sectionIdentifiers
              Precondition failed: NSArray element failed to match the Swift Array Element type
              Expected NSManagedObjectID but found __NSCFConstantString: 
              …
              
              (lldb) po snapshot.itemIdentifiers
              Precondition failed: NSArray element failed to match the Swift Array Element type
              Expected NSString but found NSTemporaryObjectID_0: 
              …
              

               

              This lets me think that NSDiffableDataSourceSnapshotReference<NSManagedObjectID, NSString> should probably be NSDiffableDataSourceSnapshotReference<NSString, NSManagedObjectID>.

            • Re: Unable to cast NSDiffableDataSourceSnapshotReference
              funkenstrahlen Level 1 Level 1 (0 points)

              Can confirm this issue when I try to use this on a UITableView. I hope Apple addresses this in a future beta. Everyone send a bugreport!

                • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                  IngmarStein Level 1 Level 1 (15 points)

                  Not yet fixed in beta 2.

                    • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                      anwill999 Level 1 Level 1 (10 points)

                      Tried in Beta 3 but no luck. Has anyone else tried in Beta 3?

                        • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                          kovapps Level 1 Level 1 (0 points)

                          I was able to get the NSDiffableDataSourceSnapshotReference working in beta 3. Here is my controller::didChangeContentWith method:

                           

                              func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference<nsstring, nsmanagedobjectid="">) {
                                  let resolvedSnapshot = NSDiffableDataSourceSnapshot<string, model="">()
                          
                                  for section in snapshot.sectionIdentifiers {
                                      resolvedSnapshot.appendSections([section as String])
                                      resolvedSnapshot.appendItems(snapshot.itemIdentifiersInSection(withIdentifier: section).map { 
                                         controller.managedObjectContext.object(with: $0) as! Model 
                                      }, toSection: section as String)
                                  }
                          
                                  dataSource.apply(resolvedSnapshot, animatingDifferences: true)
                              }

                           

                          dataSource is defined as:

                          dataSource = UICollectionViewDiffableDataSource<String,Model>(collectionView: collectionView) { ... }

                           

                          I'm curious as to whether this mapping from one snapshot to the snapshot that the dataSource expects is going to be the typical approach.

                    • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                      funkenstrahlen Level 1 Level 1 (0 points)

                      Maybe this question is related: What is the difference between NSDiffableDataSourceSnapshot and NSDiffableDataSourceSnapshotReference?

                      • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                        Shaps Level 1 Level 1 (0 points)

                        It looks as if this isn't supported directly yet. However I did manage to get it to work by pulling out the associated data. This assumes a single section so you'll need to adjust for your own implenentation however it does work and appears to update, animate etc as expected. Given the Apple sample code I was also able to add the publisher approach for cell field updates and also remove the dataSource methods

                        for numberOfItems, etc...

                         

                        <nsmanagedobjectid, nsstring=""><section, tag="">

                        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference<NSManagedObjectID, NSString>) {
                                 // NOTE: Section.main is an enum that represents my single section
                                let diffable = NSDiffableDataSourceSnapshot<Section, Tag>()
                                diffable.appendSections([.main])
                                diffable.appendItems(dataProvider.fetchedResultsController.fetchedObjects ?? [], toSection: .main)
                                dataSource.apply(diffable, animatingDifferences: true)
                            }
                        
                        • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                          mbarriault Level 1 Level 1 (0 points)

                          I'm also running into this issue, but a bit of type finagling I have it mostly working. My remaining issue is that I can't figure out how to properly populate the collection view on load.

                           

                          My type order is, as IngmarStein suggests, string before object ID,

                           

                              typealias Snapshot = NSDiffableDataSourceSnapshotReference<NSString, NSManagedObjectID>
                              typealias DataSource = UICollectionViewDiffableDataSourceReference<NSString, NSManagedObjectID>
                          

                           

                          extension ViewController: NSFetchedResultsControllerDelegate {
                              func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference<NSManagedObjectID, NSString>) {
                                  let newSnapshot = Snapshot()
                                  newSnapshot.appendSections(withIdentifiers: snapshot.sectionIdentifiers as [AnyObject] as! [NSString])
                                  newSnapshot.appendItems(withIdentifiers: snapshot.itemIdentifiers as [AnyObject] as! [NSManagedObjectID])
                                  self.dataSource.applySnapshot(newSnapshot, animatingDifferences: true)
                              }
                          }
                          

                          The secionIdentifiers are constant each launch, but I can't see how to get them out of NSFetchedResultsController without calling didChangeContent. Notably, section.name is nil. I can populated on view load by inventing temporary section titles

                           

                                  let snapshot = Snapshot()
                                  if let sections = self.fetched.sections {
                                      let sectionMap = [NSString: NSFetchedResultsSectionInfo](uniqueKeysWithValues: sections.map {section -> (NSString, NSFetchedResultsSectionInfo) in return (UUID().description as NSString, section)})
                                      snapshot.appendSections(withIdentifiers: sectionMap.compactMap {$0.key})
                                      for section in sectionMap {
                                          snapshot.appendItems(withIdentifiers: (section.value.objects as! [NSManagedObject]).map {$0.objectID}, intoSectionWithIdentifier: section.key)
                                      }
                                  }
                                  self.dataSource.applySnapshot(snapshot, animatingDifferences: false)
                          

                          These IDs will be replaced on first change. I don't know what effect this might have on a more complicated fetch, but so far it seems to not animate a large rearrangement of existing objects into a new section.

                           

                          I have also submitted a feedback request, FB6162037.

                          • Re: Unable to cast NSDiffableDataSourceSnapshotReference
                            mattn Level 1 Level 1 (10 points)

                            It's a straightforward cast:

                             

                            func controller(_ controller: NSFetchedResultsController, 
                              didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
                                let snapshot = snapshot as NSDiffableDataSourceSnapshot<string,nsmanagedobjectid>
                                self.ds.apply(snapshot, animatingDifferences: false)
                            }

                             

                            I'm assuming here that your diffable data source is typed with generic resolutions String, NSManagedObjectID. In your cell population function you go back to the fetched results controller, fetch out the actual object by index path, and populate the cell from its properties.