Search code examples
jsonswiftcore-datauicollectionviewreloaddata

CollectionView.reloadData() outputs cells in incorrect order


I am working on an app that requires a sync to the server after logging in to get all the activities the user has created and saved to the server. Currently, when the user logs in a getActivity() function that makes an API request and returns a response which is then handled.

Say the user has 4 activities saved on the server in this order (The order is determined by the time of the activity being created / saved) ;

  • Test
  • Bob
  • cvb
  • Testing

looking at the JSONHandler.getActivityResponse , it appears as though the the results are in the correct order. If the request was successful, on the home page where these activities are to be displayed, I currently loop through them like so;

WebAPIHandler.shared.getActivityRequest(completion:
        {
            success, results in DispatchQueue.main.async {
                    if(success)
                    {
                        for _ in (results)!
                        {
                            guard let managedObjectContext = self.managedObjectContext else { return }

                            let activity = Activity(context: managedObjectContext)

                            activity.name = results![WebAPIHandler.shared.idCount].name
                            print("activity name is - \(activity.name)")
                            WebAPIHandler.shared.idCount += 1
                        }
                    }

And the print within the for loop is also outputting in the expected order;

activity name is - Optional("Test")
activity name is - Optional("Bob")
activity name is - Optional("cvb")
activity name is - Optional("Testing")

The CollectionView does then insert new cells, but it seemingly in the wrong order. I'm using a carousel layout on the home page, and the 'cvb' object for example is appearing first in the list, and 'bob' is third in the list. I am using the following

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

        if var indexPath = newIndexPath
        {
//                    var itemCount = 0
//                    var arrayWithIndexPaths: [IndexPath] = []
//                
//                    for _ in 0..<(WebAPIHandler.shared.idCount)
//                    {
//                        itemCount += 1
//                        
//                        arrayWithIndexPaths.append(IndexPath(item: itemCount - 1, section: 0))
//                        print("itemCount = \(itemCount)")
//                }

            print("Insert object")
//                walkThroughCollectionView.insertItems(at: arrayWithIndexPaths)
            walkThroughCollectionView.reloadData()

    }

You can see why I've tried to use collectionView.insertItems() but that would cause an error stating:

Invalid update: invalid number of items in section 0.  The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (4), plus or minus the number of items inserted or deleted from that section (4 inserted, 0 deleted)

I saw a lot of other answers mentioning how reloadData() would fix the issue, but I'm real stuck at this point. I've been using swift for several months now, and this has been the first time I'm truly at a loss. What I also realised is that the order displayed in the carousel is also different to a separate viewController which is passed the same data. I just have no idea why the results return in the correct order, but are then displayed in an incorrect order. Is there a way to sort data in the collectionView after calling reloadData() or am I looking at this from the wrong angle?

Any help would be much appreciated, cheers!


Solution

  • The order of the collection view is specified by the sort descriptor(s) of the fetched results controller.

    Usually the workflow of inserting a new NSManagedObject is

    • Insert the new object into the managed object context.
    • Save the context. This calls the delegate methods controllerWillChangeContent, controller(:didChange:at: etc.
    • In controller(:didChange:at: insert the cell into the collection view with insertItems(at:, nothing else. Do not call reloadData() in this method.