本文介绍了从UISearchController的筛选搜索结果中删除的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 tableView CoreData 中获取其单元格内容,并且一直在替换 SearchDisplayController (不建议使用)使用新的 SearchController 。我使用相同的 tableView 控制器来显示完整的对象列表以及过滤/搜索的对象。



我设法使搜索/过滤正常工作,可以从过滤后的列表移动到这些项目的详细视图,然后编辑并将更改成功保存回过滤后的tableView。我的问题是滑动以从筛选列表中删除单元格导致运行时错误。以前使用 SearchDisplayController 我可以轻松地执行此操作,因为我可以访问 SearchDisplayController的结果表查看以及以下内容(伪)代码可以正常工作:

  func controllerDidChangeContent(controller:NSFetchedResultsController){
//如果搜索处于活动状态这个
searchDisplayController!.searchResultsTableView.endUpdates()
//否则它不活动所以这样做
tableView.endUpdates()
}
}

很遗憾没有为 UISearchController 和Im公开这样的tableView不知所措。我试过使tableView不是搜索tableView的 tableView.beginUpdates() tableView.endUpdates()条件但没有成功。



对于记录,这是我的错误信息:



断言失败 - [UITableView _endCellAnimationsWithContext:],/ SourceCache / UIKit_Sim / UIKit-3318.65 / UITableView.m:1582



*编辑*



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

  var searchController:UISearchController! 

然后在ViewDidLoad

  searchController = UISearchController(searchResultsController:无)
searchController.dimsBackgroundDuringPresentation =假
searchController.searchResultsUpdater =自
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
如果让searchText = searchText {
searchPredicate = searchText.isEmpty? nil:NSPredicate(格式:locationName包含[c]%@,searchText)
self.tableView.reloadData()
}
}

就错误信息而言,我不确定我能添加多少。按下通过滑动显示的红色删除按钮(仍然显示)后,应用程序立即挂起。这是1 - 5的线程错误日志。该应用程序似乎挂在4号。

 #0 0x00000001042fab8a在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 *

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

覆盖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方法

var fetchedResultsController:NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}

let fetchRequest = NSFetchRequest()
//根据需要编辑实体名称。
let entity = NSEntityDescription.entityForName(Location,inManagedObjectContext:self.managedObjectContext!)
fetchRequest.entity = entity

//将批量大小设置为合适的数字。
fetchRequest.fetchBatchSize = 20

//根据需要编辑排序键。
if sectionNameKeyPathString1!= nil {
let sortDescriptor1 = NSSortDescriptor(key:sectionNameKeyPathString1!,ascending:true)
let sortDescriptor2 = NSSortDescriptor(key:sectionNameKeyPathString2!,ascending:true)
fetchRequest.sortDescriptors = [sortDescriptor1,sortDescriptor2]
}否则{
让sortDescriptor = NSSortDescriptor(键: firstLetter,上升:真)
fetchRequest.sortDescriptors = [sortDescriptor]
}

var sectionNameKeyPath:String
if sectionNameKeyPathString1 == nil {
sectionNameKeyPath =firstLetter
} else {
sectionNameKeyPath = sectionNameKeyPathString1!
}

//如果合适,编辑部分名称密钥路径和缓存名称。
// nil用于节名称键路径表示无节。
让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控制器(控制器:NSFetchedResultsController,didChangeSection sectionInfo:NSFetchedResultsSectionInfo,atIndex sectionIndex:Int,forChangeType type :NSFetchedResultsChangeType){
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView =(searchController.searchResultsUpdater作为LocationViewController).tableView
}

切换类型{
case .Insert:
tableView.insertSections(NSIndexSet(index:sectionIndex),withRowAnimation:.Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index:sectionIndex),withRowAnimation:.Fade)
default:
return
}
}

func控制器(控制器:NSFetchedResultsController,didChangeObject anObject:AnyObject,atIndexPath indexPath:NSIndexPath,forChangeType类型:NSFetchedResultsChangeType,newIndexPath:NSIndexPath){
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView =(searchController.searchResultsUpdater as LocationViewController).tableView
}

switch type {
case。插入:
println(*** NSFetchedResultsChangeInsert(object))
tableView.insertRowsAtIndexPaths([newIndexPath],withRowAnimation:.Fade)

case .Delete:
println(*** NSFetchedResultsChangeDelete(object))
tableView.deleteRowsAtIndexPaths([indexPath],withRo wAnimation:.Fade)
case。更新:
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 (对象))
tableView.deleteRowsAtIndexPaths([indexPath],withRowAnimation:.Fade)
tableView.insertRowsAtIndexPaths([newIndexPath],withRowAnimation:.Fade)
}
}

func controllerDidChangeContent(控制器:NSFetchedResultsControlle r){
if searchPredicate == nil {
tableView.endUpdates()
} else {
(searchController.searchResultsUpdater as LocationViewController).tableView.endUpdates()
}
}


解决方案

问题出现是因为获取的结果控制器使用的indexPath与tableView中相应行的indexPath不匹配。



虽然搜索控制器处于活动状态,但现有的tableView被重用于显示搜索结果。因此,你的逻辑是区分两个tableViews:

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

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

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

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

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



因此,要解决此问题,请修改 commitEditingStyle ,以便在searchController处于活动状态时找到要删除的正确对象:

 倍率FUNC的tableView(的tableView:UITableView的,commitEditingStyle editingStyle:UITableViewCellEditingStyle,forRowAtIndexPath indexPath:NSIndexPath){
如果editingStyle == .Delete {
VAR位置:位置
如果searchPredicate == nil {
location = self.fetchedResultsController.objectAtIndexPath(indexPath)as Location
} else {
let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
返回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委托/数据源方法中可能需要进行类似的更改。


I have a tableView sourcing its cell content from CoreData and have been replacing the SearchDisplayController (deprecated) with the new SearchController. I am using the same tableView controller to present both the full list of objects and also the filtered/searched objects.

I have managed to get the search/filtering working fine and can move from the filtered list to detail views for those items, then edit and save changes back successfully to the filtered tableView. My problem is swiping to delete cells from the filtered list causes an run time error. Previously with the SearchDisplayController I could do this easily as I had access to the SearchDisplayController's results tableView and so the following (pseudo) code would work fine:

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

Unfortunately no such tableView is exposed for the UISearchController and Im at a loss. I have tried making the tableView.beginUpdates() and tableView.endUpdates() conditional on tableView not being the search tableView but with no success.

For the record this is my error message:

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

* EDIT *

My tableView uses a FetchedResultsController to populate itself from CoreData. This tableViewController also the one used by the SearchController to display filtered results.

var searchController: UISearchController!

Then in 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

and

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

So far as the error message is concerned, I'm not sure how much I can add. The app hangs immediately after pressing the red delete button (Which remains showing) revealed by swiping. This is the thread error log for 1 - 5. The app seems to hang on number 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) -> () ()

I hope some of this helps.

* EDIT 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()
    }
}
解决方案

The problem arises because of a mismatch between the indexPath used by the fetched results controller and the indexPath for the corresponding row in the tableView.

Whilst the search controller is active, the existing tableView is reused to display the search results. Hence your logic to differentiate the two tableViews:

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

is unnecessary. It works, because you set searchController.searchResultsUpdater = self when you initialise the searchController, so there is no need to change it, but the same tableView is used in either case.

The difference lies in the way the tableView is populated whilst the searchController is active. In that case, it looks (from the numberOfRowsInSection code) as though the filtered results are all displayed in one section. (I assume cellForRowAtIndexPath works similarly.) Suppose you delete the item at section 0, row 7, in the filtered results. Then commitEditingStyle will be called with indexPath 0-7, and the following line:

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

will try to get the object at index 0-7 from the FRC. But the item at index 0-7 of the FRC might be a completely different object. Hence you delete the wrong object. Then the FRC delegate methods fire, and tell the tableView to delete the row at index 0-7. Now, if the object really deleted was NOT in the filtered results, then the count of rows will be unchanged, even though a row has been deleted: hence the error.

So, to fix it, amend your commitEditingStyle so that it finds the correct object to delete, if the searchController is active:

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

I haven't been able to test the above; apologies if some errors slipped in. But it should at least point in the right direction; hope it helps. Note that similar changes may be required in some of the other tableView delegate/datasource methods.

这篇关于从UISearchController的筛选搜索结果中删除的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 22:52