从UISearchController的已过滤search结果中删除

我有一个tableView来源于CoreData单元格内容,并用新的SearchControllerreplace了SearchDisplayController (已弃用)。 我使用相同的tableView控制器来呈现对象的完整列表以及过滤/search的对象。

我设法使search/过滤工作正常,并可以从过滤列表移动到这些项目的详细视图,然后编辑并保存更改成功回到过滤的tableView。 我的问题是滑动删除过滤列表中的单元格导致运行时错误。 以前与SearchDisplayController我可以做到这一点,因为我有权访问SearchDisplayController's结果tableView,所以下面的(伪)代码将正常工作:

 func controllerDidChangeContent(controller: NSFetchedResultsController) { // If the search is active do this searchDisplayController!.searchResultsTableView.endUpdates() // else it isn't active so do this tableView.endUpdates() } } 

不幸的是,没有这样的tableView是暴露的UISearchController和Im在一个损失。 我已经尝试使tableView.beginUpdates()tableView.endUpdates()条件不是searchtableView,但没有成功。

logging这是我的错误消息:

Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.65/UITableView.m:1582

*编辑*

我的tableView使用FetchedResultsController从CoreData自己填充。 这个tableViewController也是SearchController用来显示过滤结果的一个。

 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() } } 

至于错误信息,我不知道我可以添加多less。 按下红色删除button(仍然显示)后,应用程序会立即挂起。 这是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之间不匹配。

在search控制器处于活动状态的同时,现有的tableView被重用来显示search结果。 因此,你的逻辑来区分两个tableViews:

 if searchPredicate == nil { tableView = self.tableView } else { tableView = (searchController.searchResultsUpdater as LocationViewController).tableView } 

是不必要的。 它起作用了,因为你在初始化searchController时设置了searchController.searchResultsUpdater = self ,所以不需要改变它,但是在任何一种情况下都使用相同的tableView。

不同之处在于在searchController处于活动状态时填充tableView的方式。 在这种情况下,它看起来(从numberOfRowsInSection代码)好像过滤结果都显示在一个部分。 (我假设cellForRowAtIndexPath工作方式类似)。假设您在过滤结果中删除了第0行第7行的项目。 然后,将使用indexPath 0-7调用commitEditingStyle ,并在下面一行:

 let location: Location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location 

将尝试从FRC获取索引0-7处的对象。 但FRC索引0-7处的项目可能是完全不同的对象。 因此你删除了错误的对象。 然后FRC委托方法触发,并通知tableView删除索引0-7处的行。 现在,如果真正删除的对象不在过滤结果中,那么即使删除了一行,行的数量也将保持不变:因此出错。

所以,为了解决这个问题,修改你的commitEditingStyle以便find正确的对象来删除,如果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() } } } 

我还没有能够testing以上; 如果出现一些错误,我们表示歉意。但至less应该指出正确的方向。 希望能帮助到你。 请注意,其他一些tableView委托/数据源方法可能需要进行类似的更改。