NSFetchedResultsController 填充和更改 UITableView 行和部分时出错
Error as NSFetchedResultsController populates and changes UITableView Rows and Sections
首先让我说这是我第一次 post 访问 Stack Overflow,这是我的第一个 iOS 应用程序。
应用程序
我和我的朋友们喜欢玩Carcassonne。我们决定开始一个正在进行的联赛来跟踪胜利和分数。此应用会跟踪这些游戏并在我们的群聊中分享结果。
数据
我正在使用 CoreData 存储三个实体:Season、Game 和 Player。以下是它们的属性/关系:
screenshot of xcdatamodel graph view.
视图控制器
我按照 this guide 将 NSFetchedResultsController 连接到 UITableView。我的 UITableViewControllers 包装在导航控制器中。我的代码片段如下。
目标
在 Navigation Controller 中导航离开后,能够在 Playing 和 Bench 部分之间切换球员(以防我需要在显示记分板后进行编辑)。
结果
预期:点击播放器名称应该切换其 isPlaying 属性并在 UITableView 的两个部分之间移动它们。
实际:在离开并返回到 UITableView 后点击玩家名称会使应用程序崩溃。
错误
我让 tableView 的 didSelectRowAt 切换播放器的 isPlaying 布尔属性。 isPlaying 将确定 Player 的行将位于 UITableView 的哪个部分。当我创建一个新游戏时,我可以将球员从板凳区 (isPlaying = false) 来回移动到比赛区 (isPlaying = true) 就好了。但是,当我离开此视图(例如,转到我的排名页面)并在导航控制器中返回它时,当我再次尝试 select 一行时应用程序崩溃。
2020-08-28 11:53:04.354939-0400 FetchTest[5427:86101] *** Assertion failure in -[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] [error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null)
CoreData: fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null)
2020-08-28 11:53:04.355397-0400 FetchTest[5427:86101] [error] CoreData: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null)
2020-08-28 11:53:04.357387-0400 FetchTest[5427:86101] [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null)
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null)
2020-08-28 11:53:04.361285-0400 FetchTest[5427:86101] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 4 from section 0 which only contains 4 rows before the update'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23e3de6e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff512539b2 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23e3dbe8 +[NSException raise:format:arguments:] + 88
3 Foundation 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 CoreData 0x00007fff23b7d69d __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 7591
9 CoreData 0x00007fff23a0338d developerSubmittedBlockToNSManagedObjectContextPerform + 154
10 CoreData 0x00007fff23a03274 -[NSManagedObjectContext performBlockAndWait:] + 197
11 CoreData 0x00007fff23b7b8e4 -[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:] + 105
12 CoreFoundation 0x00007fff23d68d2c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
13 CoreFoundation 0x00007fff23d681a5 _CFXRegistrationPost1 + 421
14 CoreFoundation 0x00007fff23d67f11 ___CFXNotificationPost_block_invoke + 193
15 CoreFoundation 0x00007fff23e65473 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1795
16 CoreFoundation 0x00007fff23d67866 _CFXNotificationPost + 950
17 Foundation 0x00007fff2593826b -[NSNotificationCenter postNotificationName:object:userInfo:] + 59
18 CoreData 0x00007fff239efaa2 -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 541
19 CoreData 0x00007fff23a9380f -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1557
20 CoreData 0x00007fff239ea599 -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 1217
21 CoreData 0x00007fff239ed8ff -[NSManagedObjectContext save:] + 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:animated:scrollPosition:notifyDelegate:isCellMultiSelect:] + 1354
26 UIKitCore 0x00007fff49520d7d -[UITableView _selectRowAtIndexPath:animated: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 CoreFoundation 0x00007fff23da1087 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
32 CoreFoundation 0x00007fff23d9bb3e __CFRunLoopDoObservers + 430
33 CoreFoundation 0x00007fff23d9c08a __CFRunLoopRun + 1226
34 CoreFoundation 0x00007fff23d9b8a4 CFRunLoopRunSpecific + 404
35 GraphicsServices 0x00007fff38c39bbe GSEventRunModal + 139
36 UIKitCore 0x00007fff49325968 UIApplicationMain + 1605
37 FetchTest 0x0000000109de0ceb main + 75
38 libdyld.dylib 0x00007fff520ce1fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
代码
我有:
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>!
在class和
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
}
正如我上面提到的,这是我的第一个 post 也是第一个应用程序。我是一个自学成才的程序员,刚刚学习 iOS 和 Swift,所以请放轻松 :)
我的控制器 didChange anObject(在 NSFetchedResultsControllerDelegate 方法部分)正在触发 .move,然后是相同 indexPath 的 .update。 .move 触发后,该 indexPath 处不再有对象,因此 .update 失败。
这就是为什么只有在点击视频的最后一行时才会失败。对于我的代码,我没有使用 .update,所以我只是将其注释掉。
首先让我说这是我第一次 post 访问 Stack Overflow,这是我的第一个 iOS 应用程序。
应用程序
我和我的朋友们喜欢玩Carcassonne。我们决定开始一个正在进行的联赛来跟踪胜利和分数。此应用会跟踪这些游戏并在我们的群聊中分享结果。
数据
我正在使用 CoreData 存储三个实体:Season、Game 和 Player。以下是它们的属性/关系: screenshot of xcdatamodel graph view.
视图控制器
我按照 this guide 将 NSFetchedResultsController 连接到 UITableView。我的 UITableViewControllers 包装在导航控制器中。我的代码片段如下。
目标
在 Navigation Controller 中导航离开后,能够在 Playing 和 Bench 部分之间切换球员(以防我需要在显示记分板后进行编辑)。
结果
预期:点击播放器名称应该切换其 isPlaying 属性并在 UITableView 的两个部分之间移动它们。
实际:在离开并返回到 UITableView 后点击玩家名称会使应用程序崩溃。
错误
我让 tableView 的 didSelectRowAt 切换播放器的 isPlaying 布尔属性。 isPlaying 将确定 Player 的行将位于 UITableView 的哪个部分。当我创建一个新游戏时,我可以将球员从板凳区 (isPlaying = false) 来回移动到比赛区 (isPlaying = true) 就好了。但是,当我离开此视图(例如,转到我的排名页面)并在导航控制器中返回它时,当我再次尝试 select 一行时应用程序崩溃。
2020-08-28 11:53:04.354939-0400 FetchTest[5427:86101] *** Assertion failure in -[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] [error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null) CoreData: fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null) 2020-08-28 11:53:04.355397-0400 FetchTest[5427:86101] [error] CoreData: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null) 2020-08-28 11:53:04.357387-0400 FetchTest[5427:86101] [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null) CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. attempt to delete row 4 from section 0 which only contains 4 rows before the update with userInfo (null) 2020-08-28 11:53:04.361285-0400 FetchTest[5427:86101] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 4 from section 0 which only contains 4 rows before the update' *** First throw call stack: ( 0 CoreFoundation 0x00007fff23e3de6e __exceptionPreprocess + 350 1 libobjc.A.dylib 0x00007fff512539b2 objc_exception_throw + 48 2 CoreFoundation 0x00007fff23e3dbe8 +[NSException raise:format:arguments:] + 88 3 Foundation 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 CoreData 0x00007fff23b7d69d __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 7591 9 CoreData 0x00007fff23a0338d developerSubmittedBlockToNSManagedObjectContextPerform + 154 10 CoreData 0x00007fff23a03274 -[NSManagedObjectContext performBlockAndWait:] + 197 11 CoreData 0x00007fff23b7b8e4 -[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:] + 105 12 CoreFoundation 0x00007fff23d68d2c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12 13 CoreFoundation 0x00007fff23d681a5 _CFXRegistrationPost1 + 421 14 CoreFoundation 0x00007fff23d67f11 ___CFXNotificationPost_block_invoke + 193 15 CoreFoundation 0x00007fff23e65473 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1795 16 CoreFoundation 0x00007fff23d67866 _CFXNotificationPost + 950 17 Foundation 0x00007fff2593826b -[NSNotificationCenter postNotificationName:object:userInfo:] + 59 18 CoreData 0x00007fff239efaa2 -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 541 19 CoreData 0x00007fff23a9380f -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1557 20 CoreData 0x00007fff239ea599 -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 1217 21 CoreData 0x00007fff239ed8ff -[NSManagedObjectContext save:] + 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:animated:scrollPosition:notifyDelegate:isCellMultiSelect:] + 1354 26 UIKitCore 0x00007fff49520d7d -[UITableView _selectRowAtIndexPath:animated: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 CoreFoundation 0x00007fff23da1087 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23 32 CoreFoundation 0x00007fff23d9bb3e __CFRunLoopDoObservers + 430 33 CoreFoundation 0x00007fff23d9c08a __CFRunLoopRun + 1226 34 CoreFoundation 0x00007fff23d9b8a4 CFRunLoopRunSpecific + 404 35 GraphicsServices 0x00007fff38c39bbe GSEventRunModal + 139 36 UIKitCore 0x00007fff49325968 UIApplicationMain + 1605 37 FetchTest 0x0000000109de0ceb main + 75 38 libdyld.dylib 0x00007fff520ce1fd start + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException (lldb)
代码
我有:
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>!
在class和
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
}
正如我上面提到的,这是我的第一个 post 也是第一个应用程序。我是一个自学成才的程序员,刚刚学习 iOS 和 Swift,所以请放轻松 :)
我的控制器 didChange anObject(在 NSFetchedResultsControllerDelegate 方法部分)正在触发 .move,然后是相同 indexPath 的 .update。 .move 触发后,该 indexPath 处不再有对象,因此 .update 失败。
这就是为什么只有在点击视频的最后一行时才会失败。对于我的代码,我没有使用 .update,所以我只是将其注释掉。