【问题标题】:Deleting from UISearchController's filtered search results从 UISearchController 中删除过滤的搜索结果
【发布时间】:2014-12-26 11:44:19
【问题描述】:

我有一个tableViewCoreData 采购其单元格内容,并且已将SearchDisplayController(已弃用)替换为新的SearchController。我正在使用相同的 tableView 控制器来呈现完整的对象列表以及过滤/搜索的对象。

我已经设法使搜索/过滤工作正常,并且可以从过滤列表移动到这些项目的详细视图,然后编辑并将更改成功保存回过滤后的 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,我不知所措。我已经尝试使tableView.beginUpdates()tableView.endUpdates() 以tableView 为条件而不是搜索tableView 但没有成功。

为了记录,这是我的错误信息:

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

就错误消息而言,我不确定我可以添加多少。按下通过滑动显示的红色删除按钮(仍显示)后,该应用程序立即挂起。这是 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()
    }
}

【问题讨论】:

  • 请您分享您的 SearchController 配置、updateSearchResultsForSearchController 方法的更多详细信息,以及有关错误消息的更多信息?谢谢。
  • 我在原始答案中添加了更多信息。希望这会有所帮助。
  • 谢谢。尽管在调用 tableView.endUpdates() 时错误会具体化,但我认为问题出在其他地方。我怀疑问题是删除后numberOfRowsInSection返回的数字与之前的值不一致(如果你删除了一行,应该是(之前的值-1)。检查(和/或添加到你的问题)commitEditingStylenumberOfRowsInSection 中的代码以及其他 FRC 委托方法。
  • 添加了更多代码。我认为您对行数不一致的看法是正确的,但现在我觉得我淹没在这段代码中。

标签: ios uitableview swift uisearchdisplaycontroller uisearchcontroller


【解决方案1】:

问题是由于获取结果控制器使用的 indexPath 与 tableView 中相应行的 indexPath 不匹配造成的。

当搜索控制器处于活动状态时,现有的 tableView 被重用来显示搜索结果。因此,您区分两个 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 行的项目。然后 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 委托/数据源方法可能需要进行类似的更改。

【讨论】:

  • 非常感谢您花时间和精力如此清楚地解释这一点。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多