NSFetchedResultsControllerDelegate not triggered when adding first item to DB.Triggered only when I added a second item

一曲冷凌霜 提交于 2020-04-17 22:06:21

问题


My application has two tab bars. The first one presents a list of games added on view controller and save them on the core data database. Switching on the second tab/view reads from the database and presents it inside a table view. I implemented the NSFetchedResultsControllerDelegate with a fetch method. When I add the first item to the context on the first tab and switch to second tab, FRC delegate methods (controllerWillChangeContent(_:), controller(_:didChange:at:for:newIndexPath:), controllerDidChangeContent(_:)) are not getting called and the table view is empty while I can see arrayOfGamesCount = 1. But when I add a second item, I can see all FRC delegate methods are getting call when I switch to second tab bar. And TableView display one rows while arrayOfGamesCount = 2

The first tab bar have 2 view controllers.(AddGameViewController and WelcomeViewController) AddGameVC is used to grab data from textfields and send it to welcomeVC.

import UIKit
import CoreData
    class WelcomeViewController: UIViewController,SendGameDataDelegate, UIAdaptivePresentationControllerDelegate {


        var games : [Game]? = []
        var gamesMo: [GameMo]? = []
        var gamed: GameMo?
        var game : Game?



        func ShouldSendGame(game: Game) {
            self.game = game
            print("\(game)")
            games?.append(game)

        }

        @IBAction func endWLButton(_ sender: UIButton) {


            saveDataToCoreData()
            print("number of games from gamesMoCount is \(gamesMo?.count ?? 0)")

            games?.removeAll()
            reloadCollectionViewData()

        }


        func saveDataToCoreData (){
            if  let appDelegate = UIApplication.shared.delegate as? AppDelegate {
                gamed = GameMo(context: appDelegate.persistentContainer.viewContext)
                if games != nil {
                    for game in games! {
                        gamed?.goal = Int32(game.goal ?? 0 )
                        gamed?.rivalGoal = Int32(game.rivalGoal ?? 0)
                        gamed?.shot = Int32(game.shots ?? 0)
                        gamed?.rivalShot = Int32(game.rivalGoal ?? 0)
                        gamed?.rivalCorners = Int32(game.rivalsCorner ?? 0)
                        gamed?.corners = Int32(game.corners ?? 0)
                        gamesMo?.append(gamed!)
                    }

                    print("Saving data to context ....")
                    appDelegate.saveContext()
                }
            }
        }
    }
    extension WelcomeViewController: UICollectionViewDelegate, UICollectionViewDataSource {

        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return games?.count ?? 0
        }

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            if let gameIndex = games?[indexPath.row] {
                let userGameScore = gameIndex.goal
                let rivalGameScore = gameIndex.rivalGoal
                if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FormCell", for: indexPath) as? FormCollectionViewCell {
                    cell.setCell(userScores: userGameScore!, rivalScores: rivalGameScore! )

                    return cell
                }

            }
            return UICollectionViewCell ()

        }
    }

The second tab bar have only one VC: AllWLeagueController used to display items from the the database.

 import UIKit
    import CoreData

    class AllWLeagueController : UITableViewController {

        var fetchRequestController : NSFetchedResultsController<GameMo>!
        var arrayOfGamesModel : [[GameMo]]? = []
        var gameMo: GameMo?
        var gamesMo: [GameMo] = []


        override func viewDidLoad() {
            validation(object: arrayOfGamesModel)

        }

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            fetchRequest()
        }



        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
             print("arrayOfGamesModelcount est \(arrayOfGamesModel?.count ?? 0)")

            return arrayOfGamesModel?.count ?? 0
        }
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {



            if let weekL = arrayOfGamesModel?[indexPath.row] {
                if let cell = tableView.dequeueReusableCell(withIdentifier: "WL") as? AllWLeaguesTableViewCell {
                    let winCounts = WLManager.winCountMethod(from: weekL)
                    let lossCounts = WLManager.lossCountMethod(from:weekL)
                    cell.setOulet(win: winCounts, loss: lossCounts, rankName: rankString)

                    cellLayer(with: cell)
                    return cell
                }
            }

   }



  extension AllWLeagueController: NSFetchedResultsControllerDelegate {

        func fetchRequest ()  {
            let fetchRequest = NSFetchRequest<GameMo>(entityName: "Game")

            fetchRequest.sortDescriptors = [NSSortDescriptor(key: "win", ascending: true)]

            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate){
                let context = appDelegate.persistentContainer.viewContext

                // fetch result controller
                fetchRequestController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
                fetchRequestController.delegate = self

                do{
                    try fetchRequestController.performFetch()

                    if let fetchedObjects = fetchRequestController.fetchedObjects {
                        gamesMo = fetchedObjects


                        print("Fetech Request Activated")
                        print(gamesMo)
                    }
                }catch{
                    fatalError("Failed to fetch entities: \(error)")
                }
            }

        }


        func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            print("TableView beginupdates")
            tableView.beginUpdates()
        }

        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
            switch type {
            case .insert:
                if let newIndexPath = newIndexPath {
                    print("insert")
                    tableView.insertRows(at: [newIndexPath], with: .fade)

                }
            case .delete:
                if let indexPath = indexPath {
                     print("delete")
                    tableView.deleteRows(at: [indexPath], with: .fade)
                }
            case .update:
                if let indexPath = indexPath {
                     print("update")
                    tableView.reloadRows(at: [indexPath], with: .fade)

                }

            default:
                tableView.reloadData()
            }

            if let fetchedObjects = controller.fetchedObjects {
                gamesMo = fetchedObjects as! [GameMo]
                print("we are about to append arrayOfGamesModel")
                   arrayOfGamesModel?.append(gamesMo)

            }
        }


        func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            print("TableView endupdates")
            tableView.endUpdates()
        }
     }

回答1:


You are making a fatal mistake. In saveDataToCoreData only one instance is created and then it's being overwritten with the game data in each iteration of the array. So your array gamesMo may contain multiple items but it's always the same instance and only one instance is saved into the context.

Replace saveDataToCoreData with

func saveDataToCoreData (){
   let appDelegate = UIApplication.shared.delegate as! AppDelegate 
   guard let games = games else { return }
   for game in games {
       let newGame = GameMo(context: appDelegate.persistentContainer.viewContext)
        newGame.goal = Int32(game.goal ?? 0 )
        newGame.rivalGoal = Int32(game.rivalGoal ?? 0)
        newGame.shot = Int32(game.shots ?? 0)
        newGame.rivalShot = Int32(game.rivalGoal ?? 0)
        newGame.rivalCorners = Int32(game.rivalsCorner ?? 0)
        newGame.corners = Int32(game.corners ?? 0)
        gamesMo.append(newGame)
    }
    print("Saving data to context ....")
    appDelegate.saveContext()
}

Another bad practice is to create new fetch results controllers in viewWillAppear. It's highly recommended to create one controller as lazy instantiated property – as well as the managed object context – for example

lazy var context : NSManagedObjectContext = {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    return appDelegate.persistentContainer.viewContext
}()


lazy var fetchRequestController : NSFetchedResultsController<GameMo> = {
    let fetchRequest = NSFetchRequest<GameMo>(entityName: "Game")
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "win", ascending: true)]

    // fetch result controller
    let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
    frc.delegate = self

    do {
       try frc.performFetch()
       if let fetchedObjects = frc.fetchedObjects {
          self.gamesMo = fetchedObjects
          print("Fetech Request Activated")
          print(gamesMo)
       }
   } catch{
       fatalError("Failed to fetch entities: \(error)")
   }
   return frc
}()

Force unwrapping AppDelegate is perfectly fine. Your app won't even launch if AppDelegate was missing.

I recommend also to use less ambiguous variable names. games, gamesMo, gamed and game look very similar and can cause confusion.



来源:https://stackoverflow.com/questions/61233538/nsfetchedresultscontrollerdelegate-not-triggered-when-adding-first-item-to-db-tr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!