【问题标题】:Error as NSFetchedResultsController populates and changes UITableView Rows and SectionsNSFetchedResultsController 填充和更改 UITableView 行和部分时出错
【发布时间】:2020-08-28 17:04:06
【问题描述】:

首先让我说这是我第一次在 Stack Overflow 上发帖,这是我的第一个 iOS 应用程序。

应用程序

我和我的朋友喜欢玩Carcassonne。我们决定开始一个持续的联赛来跟踪胜利和得分。此应用会跟踪这些游戏并在我们的群聊中分享结果。

数据

我正在使用 CoreData 来存储三个实体:Season、Game 和 Player。以下是它们的属性/关系: screenshot of xcdatamodel graph view.

视图控制器

我关注this guide 将 NSFetchedResultsController 连接到 UITableView。我的 UITableViewControllers 包装在导航控制器中。我的代码片段如下。

目标

在导航控制器中导航后能够在播放和替补部分之间切换玩家(以防我在显示记分板后需要进行编辑)。

结果

预期:点击玩家姓名应该会切换他们的 isPlaying 属性,并在 UITableView 的两个部分之间移动他们。

实际:在离开并返回 UITableView 后点击玩家姓名会导致应用程序崩溃。

错误

Here is a video of the error.

我有 tableView 的 didSelectRowAt 切换播放器的 isPlaying 布尔属性。 isPlaying 将确定 Player 所在行的 UITableView 的哪个部分。当我创建一个新游戏时,我可以将球员从板凳区 (isPlaying = false) 来回移动到比赛区 (isPlaying = true) 就好了。然而,当我离开这个视图(例如,到我的排名页面)并在导航控制器中返回它时,当我再次尝试选择一行时,应用程序崩溃了。

2020-08-28 11:53:04.354939-0400 FetchTest[5427:86101] *** 断言失败 -[UITableView _endCellAnimationsWithContext:], /Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit -3920.31.102/UITableView.m:2108
2020-08-28 11:53:04.355249-0400 FetchTest[5427:86101] [错误] 错误:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了一个异常。尝试从第 0 节中删除第 4 行,该第 4 行在使用 userInfo (null) 进行更新之前仅包含 4 行
CoreData:故障:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了一个异常。尝试从第 0 节中删除第 4 行,该第 4 行在使用 userInfo (null) 进行更新之前仅包含 4 行
2020-08-28 11:53:04.355397-0400 FetchTest[5427:86101] [错误] CoreData:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了一个异常。尝试从第 0 节中删除第 4 行,该第 4 行在使用 userInfo (null) 进行更新之前仅包含 4 行
2020-08-28 11:53:04.357387-0400 FetchTest[5427:86101] [错误] 错误:严重的应用程序错误。在核心数据更改处理期间捕获到异常。这通常是 NSManagedObjectContextObjectsDidChangeNotification 观察者中的一个错误。尝试从第 0 节中删除第 4 行,该第 4 行在使用 userInfo (null) 进行更新之前仅包含 4 行
CoreData:错误:严重的应用程序错误。在核心数据更改处理期间捕获到异常。这通常是 NSManagedObjectContextObjectsDidChangeNotification 观察者中的一个错误。尝试从第 0 节中删除第 4 行,该第 4 行在使用 userInfo (null) 进行更新之前仅包含 4 行
2020-08-28 11:53:04.361285-0400 FetchTest[5427:86101] *** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“尝试从第 0 部分删除第 4 行,更新前仅包含 4 行'
*** 首先抛出调用堆栈:
(
    0 核心基础 0x00007fff23e3de6e __exceptionPreprocess + 350
    1 libobjc.A.dylib 0x00007fff512539b2 objc_exception_throw + 48
    2 CoreFoundation 0x00007fff23e3dbe8 + [NSException raise:format:arguments:] + 88
    3 基础 0x00007fff258d6bd2 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4 UIKitCore 0x00007fff4950188a-[UITableView _endCellAnimationsWithContext:] + 6824
    5 UIKitCore 0x00007fff4951dace-[UITableView endUpdatesWithContext:] + 112
    6 FetchTest 0x0000000109df538f $s9FetchTest29GameDetailTableViewControllerC26controllerDidChangeContentyySo016NSFetchedResultsG0CySo20NSFetchRequestResult_pGF + 287
    7 FetchTest 0x0000000109df53f4 $s9FetchTest29GameDetailTableViewControllerC26controllerDidChangeContentyySo016NSFetchedResultsG0CySo20NSFetchRequestResult_pGFTo + 68
    8 核心数据 0x00007fff23b7d69d __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 7591
    9 CoreData 0x00007fff23a0338d 开发者提交的BlockToNSManagedObjectContextPerform + 154
    10 核心数据 0x00007fff23a03274 -[NSManagedObjectContext performBlockAndWait:] + 197
    11核心数据0x00007fff23b7b8e4-[NSFetchedResultsController(PrivateMethods)_core_managedObjectContextDidChange:]+105
    12 核心基础 0x00007fff23d68d2c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
    13 核心基础 0x00007fff23d681a5 _CFXRegistrationPost1 + 421
    14 核心基础 0x00007fff23d67f11 ___CFXNotificationPost_block_invoke + 193
    15 核心基础 0x00007fff23e65473 -[_CFXNotificationRegistrar 查找:对象:观察者:枚举器:] + 1795
    16 核心基础 0x00007fff23d67866 _CFXNotificationPost + 950
    17 基础 0x00007fff2593826b -[NSNotificationCenter postNotificationName:object:userInfo:] + 59
    18核心数据0x00007fff239efaa2-[NSManagedObjectContext(_NSInternalNotificationHandling)_postObjectsDidChangeNotificationWithUserInfo:]+541
    19核心数据0x00007fff23a9380f-[NSManagedObjectContext(_NSInternalChangeProcessing)_createAndPostChangeNotification:删除:更新:刷新:延期:wasMerge:] + 1557
    20 核心数据 0x00007fff239ea599 -[NSManagedObjectContext(_NSInternalChangeProcessing)_processRecentChanges:] + 1217
    21 核心数据 0x00007fff239ed8ff -[NSManagedObjectContext 保存:] + 367
    22 FetchTest 0x0000000109df0b33 $s9FetchTest29GameDetailTableViewControllerC9saveGamesyyF + 131
    23 FetchTest 0x0000000109df02c7 $s9FetchTest29GameDetailTableViewControllerC05tableF0_14didSelectRowAtySo07UITableF0C_10Foundation9IndexPathVtF + 1543
    24 FetchTest 0x0000000109df0427 $s9FetchTest29GameDetailTableViewControllerC05tableF0_14didSelectRowAtySo07UITableF0C_10Foundation9IndexPathVtFTo + 167
    25 UIKitCore 0x00007fff495212de-[UITableView _selectRowAtIndexPath:动画:scrollPosition:notifyDelegate:isCellMultiSelect:] + 1354
    26 UIKitCore 0x00007fff49520d7d -[UITableView _selectRowAtIndexPath:动画:scrollPosition:notifyDelegate:] + 97
    27 UIKitCore 0x00007fff495216be -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 334
    28 UIKitCore 0x00007fff4932eb76 _runAfterCACommitDeferredBlocks + 352
    29 UIKitCore 0x00007fff4931f304 _cleanUpAfterCAFlushAndRunDeferredBlocks + 248
    30 UIKitCore 0x00007fff4934fb0d _afterCACommitHandler + 85
    31 核心基础 0x00007fff23da1087 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    32 核心基础 0x00007fff23d9bb3e __CFRunLoopDoObservers + 430
    33 核心基础 0x00007fff23d9c08a __CFRunLoopRun + 1226
    34 核心基础 0x00007fff23d9b8a4 CFRunLoopRunSpecific + 404
    35 图形服务 0x00007fff38c39bbe GSEventRunModal + 139
    36 UIKitCore 0x00007fff49325968 UIApplicationMain + 1605
    37 FetchTest 0x0000000109de0ceb 主要 + 75
    38 libdyld.dylib 0x00007fff520ce1fd 开始 + 1
)
libc++abi.dylib:以 NSException 类型的未捕获异常终止
(lldb)

代码

我有:

var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>!

在课堂上和

initializeFetchedResultsController()

在 viewDidLoad() 中调用。

这是我的 GameDetailTableViewController 中的 UITableView 方法:

    override func numberOfSections(in tableView: UITableView) -> Int {
        return fetchedResultsController.sections!.count
        
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let sections = fetchedResultsController.sections else {
            fatalError("No sections in fetchedResultsController")
        }
        let sectionInfo = sections[section]
        
        return sectionInfo.numberOfObjects
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        guard let sections = fetchedResultsController.sections else {
            fatalError("No sections in fetchedResultsController")
        }
        if sections[section].indexTitle == "0" {
            return "Bench"
        } else {
            return "Playing"
        }
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "gameDetailCell", for: indexPath) as! PlayerCell
        
        guard let object = self.fetchedResultsController?.object(at: indexPath) as? Player else {
            fatalError("Attempt to configure cell without a managed object")
        }
        
        cell.player = object
        
        cell.addDoneButtonOnKeyboard()
        
        //Set Name and Image
        
        if let name = object.name {
            cell.playerCellLabel.text = "\(name)"
            cell.playerImageView.image = maskRoundedImage(image: UIImage(named: name)!, radius: 15)
        }
        
        cell.playerScoreTextField.text = String(object.score)
        cell.playerScoreTextField.clearsOnBeginEditing = true
        cell.playerScoreTextField.delegate = self
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        guard let object = self.fetchedResultsController?.object(at: indexPath) as? Player else {
            fatalError("Attempt to configure cell without a managed object")
        }
        
        print("Row \(indexPath.row) in section \(indexPath.section) was tapped.")
        
        
        object.isPlaying = !object.isPlaying
        saveGames()
 
    }

这是我的 NSFetchedResultsController 扩展。这是基于上面链接的 Apple 指南。 (是的,initializeStandingsFetchedResultsController() 不是很干燥,这是我列表中的下一个。)我使用我的 isPlaying 布尔属性作为 sectionNameKeyPath。

extension GameDetailTableViewController: NSFetchedResultsControllerDelegate {
    // MARK: - NSFetchedResultsController
    func initializeFetchedResultsController() {
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
        let isPlayingSort = NSSortDescriptor(key: "isPlaying", ascending: false)
        let scoreSort = NSSortDescriptor(key: "score", ascending: false)
        request.sortDescriptors = [isPlayingSort, scoreSort]
        
        fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: K.context, sectionNameKeyPath: "isPlaying", cacheName: nil)
        fetchedResultsController.delegate = self
        
        fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "game.dateCreated == %@", selectedGame!.dateCreated! as CVarArg)
        do {
            try fetchedResultsController.performFetch()
            
            playerArray = fetchedResultsController.fetchedObjects as! [Player]
            
        } catch {
            fatalError("Failed to initialize FetchedResultsController: \(error)")
        }
    }
    
    func initializeStandingsFetchedResultsController() {
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
        let scoreSort = NSSortDescriptor(key: "score", ascending: false)
        request.sortDescriptors = [scoreSort]
        
        standingsFetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: K.context, sectionNameKeyPath: nil, cacheName: nil)
        standingsFetchedResultsController.delegate = self
        
        standingsFetchedResultsController.fetchRequest.predicate = NSPredicate(format: "game.dateCreated == %@", selectedGame!.dateCreated! as CVarArg)
        do {
            try standingsFetchedResultsController.performFetch()
            
            
            standingsPlayerArray = standingsFetchedResultsController.fetchedObjects as! [NSManagedObject]
            
            
        } catch {
            fatalError("Failed to initialize FetchedResultsController: \(error)")
        }
    }
    
    // MARK: - NSFetchedResultsControllerDelegate Methods
    
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        switch type {
        case .insert:
            tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
        case .delete:
            tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
        case .move:
            break
        case .update:
            break
        @unknown default:
            fatalError("You did something funky with the table view.")
        }
    }
    
    
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
            tableView.reloadRows(at: [indexPath!], with: .fade)
        case .move:
            tableView.moveRow(at: indexPath!, to: newIndexPath!)
        @unknown default:
            fatalError("You did something funky with the table view.")
        }
    }
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }
}</pre>

saveGames() is just this:
<pre>func saveGames() {
        do {
            try K.context.save()
        } catch {
            print("Error saving games. \(error)")
        }
    }</pre>
And my K.context is:
<pre>struct K {
    static let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}

正如我上面提到的,这是我的第一篇文章和第一个应用程序。我是一名自学成才的程序员,刚刚学习 iOS 和 Swift,所以请放轻松:)

【问题讨论】:

    标签: ios swift uitableview nsfetchedresultscontroller


    【解决方案1】:

    我的控制器 didChange anObject(在 NSFetchedResultsControllerDelegate 方法部分中)为相同的 indexPath 触发了 .move,然后是 .update。 .move 触发后,该 indexPath 上不再有对象,因此 .update 失败。

    这就是为什么它只在点击视频的最后一行时失败。对于我的代码,我没有使用 .update,所以我只是将其注释掉。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-15
      • 1970-01-01
      • 2012-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多