我有一个tableView
从CoreData
获取其单元格内容,并一直在用新SearchController
替换SearchDisplayController
(已弃用)。我使用相同的tableView
控制器来显示对象的完整列表以及过滤/搜索的对象。
我已经设法使搜索/过滤工作正常,并且可以从过滤列表移动到这些项目的详细信息视图,然后编辑更改并成功保存回过滤后的表视图。我的问题是滑动以从过滤列表中删除单元格会导致运行时错误。以前使用 SearchDisplayController
我可以轻松做到这一点,因为我可以访问SearchDisplayController's
结果表视图,因此以下(伪)代码可以正常工作:
func controllerDidChangeContent(controller: NSFetchedResultsController) {
// If the search is active do this
searchDisplayController!.searchResultsTableView.endUpdates()
// else it isn't active so do this
tableView.endUpdates()
}
}
不幸的是,没有这样的表视图暴露给UISearchController
,我不知所措。我尝试使tableView.beginUpdates()
和tableView.endUpdates()
条件是表视图不是搜索表视图,但没有成功。
作为记录,这是我的错误消息:
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.65/UITableView.m:1582
*编辑*
My tableView 使用 FetchedResultsController 从 CoreData 填充自身。此表视图控制器也是搜索控制器用来显示筛选结果的控制器。
var searchController: UISearchController!
然后在ViewDidLoad中
searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = searchController?.searchBar
self.tableView.delegate = self
self.definesPresentationContext = true
和
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = self.searchController?.searchBar.text
if let searchText = searchText {
searchPredicate = searchText.isEmpty ? nil : NSPredicate(format: "locationName contains[c] %@", searchText)
self.tableView.reloadData()
}
}
就错误消息而言,我不确定我可以添加多少。按下通过滑动显示的红色删除按钮(仍然显示)后,该应用程序立即挂起。这是 1 - 5 的线程错误日志。该应用程序似乎挂在数字 4 上。
#0 0x00000001042fab8a in objc_exception_throw ()
#1 0x000000010204b9da in +[NSException raise:format:arguments:] ()
#2 0x00000001027b14cf in -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] ()
#3 0x000000010311169a in -[UITableView _endCellAnimationsWithContext:] ()
#4 0x00000001019b16f3 in iLocations.LocationViewController.controllerDidChangeContent (iLocations.LocationViewController)(ObjectiveC.NSFetchedResultsController) -> () at /Users/neilmckay/Dropbox/Programming/My Projects/iLocations/iLocations/LocationViewController.swift:303
#5 0x00000001019b178a in @objc iLocations.LocationViewController.controllerDidChangeContent (iLocations.LocationViewController)(ObjectiveC.NSFetchedResultsController) -> () ()
我希望其中一些有所帮助。
*编辑 2 *
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let location: Location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location
location.removePhotoFile()
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(location)
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchPredicate == nil {
let sectionInfo = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
} else {
let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
}
return filteredObjects == nil ? 0 : filteredObjects!.count
}
}
// MARK: - NSFetchedResultsController methods
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Location", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
if sectionNameKeyPathString1 != nil {
let sortDescriptor1 = NSSortDescriptor(key: sectionNameKeyPathString1!, ascending: true)
let sortDescriptor2 = NSSortDescriptor(key: sectionNameKeyPathString2!, ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2]
} else {
let sortDescriptor = NSSortDescriptor(key: "firstLetter", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
}
var sectionNameKeyPath: String
if sectionNameKeyPathString1 == nil {
sectionNameKeyPath = "firstLetter"
} else {
sectionNameKeyPath = sectionNameKeyPathString1!
}
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil /*"Locations"*/)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
fatalCoreDataError(error)
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func controllerWillChangeContent(controller: NSFetchedResultsController) {
if searchPredicate == nil {
tableView.beginUpdates()
} else {
(searchController.searchResultsUpdater as LocationViewController).tableView.beginUpdates()
}
//tableView.beginUpdates() }
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as LocationViewController).tableView
}
switch type {
case .Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath) {
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as LocationViewController).tableView
}
switch type {
case .Insert:
println("*** NSFetchedResultsChangeInsert (object)")
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
case .Delete:
println("*** NSFetchedResultsChangeDelete (object)")
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
case .Update:
println("*** NSFetchedResultsChangeUpdate (object)")
if searchPredicate == nil {
let cell = tableView.cellForRowAtIndexPath(indexPath) as LocationCell
let location = controller.objectAtIndexPath(indexPath) as Location
cell.configureForLocation(location)
} else {
let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell
let location = controller.objectAtIndexPath(searchIndexPath) as Location
cell.configureForLocation(location)
}
case .Move:
println("*** NSFetchedResultsChangeMove (object)")
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
if searchPredicate == nil {
tableView.endUpdates()
} else {
(searchController.searchResultsUpdater as LocationViewController).tableView.endUpdates()
}
}
出现此问题的原因是获取的结果控制器使用的 indexPath 与 tableView 中相应行的 indexPath 不匹配。
当搜索控制器处于活动状态时,将重复使用现有表视图来显示搜索结果。 因此,您区分两个表视图的逻辑:
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as LocationViewController).tableView
}
是不必要的。 它有效,因为您在初始化 searchController 时设置了searchController.searchResultsUpdater = self
,因此无需更改它,但在这两种情况下都使用相同的 tableView。
区别在于在搜索控制器处于活动状态时填充表视图的方式。 在这种情况下,它看起来(从numberOfRowsInSection
代码中)好像过滤的结果都显示在一个部分中。 (我认为cellForRowAtIndexPath
的工作方式类似。 假设您删除筛选结果中第 0 部分第 7 行处的项目。 然后commitEditingStyle
将使用 indexPath 0-7
和以下行调用:
let location: Location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location
将尝试从 FRC 获取索引 0-7 处的对象。 但 FRC 索引 0-7 处的项目可能是一个完全不同的对象。 因此,您删除了错误的对象。 然后,FRC 委托方法触发,并告知 tableView 删除索引 0-7 处的行。 现在,如果真正删除的对象不在过滤结果中,则即使删除了一行,行数也将保持不变:因此会出现错误。
因此,要修复它,请修改您的commitEditingStyle
,以便它找到要删除的正确对象(如果 searchController 处于活动状态):
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
var location : Location
if searchPredicate == nil {
location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location
} else {
let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
}
location = filteredObjects![indexPath.row] as Location
}
location.removePhotoFile()
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(location)
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
我无法测试上述内容;如果出现一些错误,请道歉。 但它至少应该指向正确的方向;希望对您有所帮助。 请注意,在其他一些 tableView 委托/数据源方法中可能需要类似的更改。