Swift UISearchController 在核心数据项目中连接,应用程序运行,但搜索不更新

Swift UISearchController wired up in Core Data Project, app runs, but search not updating

过去几天我一直在努力在 Swift 项目中为使用核心数据的 TableViewController 创建过滤器。我终于明白我需要使用 UISearchController,为 searchController.searchBar 创建一个 NSPredicate,等等

我找到了 this post EXTREMELY helpful,但是在根据这个项目对我的 TVC 进行建模之后,我找到了 "all the lights are on, but nobody's home"。我可以搜索,在 searchBar 中创建谓词、添加、删除等,但单元格不会针对搜索进行更新。我错过了什么,但我不知道是什么。

这是我的代码的相关部分。

class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchResultsUpdating

// Properties that get instantiated later

var detailViewController: DetailViewController? = nil
var addNoteViewController:AddNoteViewController? = nil  // I added this
var managedObjectContext: NSManagedObjectContext? = nil

// Added variable for UISearchController
var searchController: UISearchController!
var searchPredicate: NSPredicate? // I added this. It's optional on and gets set later

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationItem.leftBarButtonItem = self.editButtonItem()

        if let split = self.splitViewController {
            let controllers = split.viewControllers
            let context = self.fetchedResultsController.managedObjectContext
            let entity = self.fetchedResultsController.fetchRequest.entity!
            self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
        }

        // UISearchController setup
        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
    }

    // MARK: - UISearchResultsUpdating Delegate Method
    // Called when the search bar's text or scope has changed or when the search bar becomes first responder.
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        let searchText = self.searchController?.searchBar.text // steve put breakpoint
        println(searchController.searchBar.text)
        if let searchText = searchText {
            searchPredicate = NSPredicate(format: "noteBody contains[c] %@", searchText)
            self.tableView.reloadData()
            println(searchPredicate)
        }
    }

    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            var note: Note
            if searchPredicate == nil {
                note = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Note
            } else {
                let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
                    return self.searchPredicate!.evaluateWithObject([=11=])
                }
                note = filteredObjects![indexPath.row] as! Note
            }
            let context = self.fetchedResultsController.managedObjectContext
            context.deleteObject(note)

            var error: NSError? = nil
            if !context.save(&error) {
                abort()
            }
        }
    }

    // MARK: - Fetched results controller

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

        let fetchRequest = NSFetchRequest()
        // Edit the entity name as appropriate.
        let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!)
        fetchRequest.entity = entity

        // Set the batch size to a suitable number.
        fetchRequest.fetchBatchSize = 20

        // Edit the sort key as appropriate.
        let sortDescriptor = NSSortDescriptor(key: "noteTitle", ascending: false)
        let sortDescriptors = [sortDescriptor]

        fetchRequest.sortDescriptors = [sortDescriptor]

        // 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: nil, cacheName: "Master")
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController

        var error: NSError? = nil
        if !_fetchedResultsController!.performFetch(&error) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            println("Unresolved error \(error), \(error?.userInfo)")
            abort()
        }

        return _fetchedResultsController!
    }

    var _fetchedResultsController: NSFetchedResultsController? = nil

    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        //        self.tableView.beginUpdates() // ** original code, change if doesn't work** steve put breakpoint here
        // ANSWER said this section is redundant, but keeping it b/c it doesn't crash
        if searchPredicate == nil {
            tableView.beginUpdates()
        } else {
            (searchController.searchResultsUpdater as! MasterViewController).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! MasterViewController).tableView
        }

        switch type {
        case .Insert:
            self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        case .Delete:
            self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        default:
            return
        }
    }

我觉得我的问题 "lives" 在这一部分。到目前为止,自动完成一直是我的朋友,但我没有看到自动完成中引用了“searchIndex”。 我想我错过了什么,但我不确定是什么或如何。

如果您已经读到这里,感谢您的阅读。 Here's the GitHub repo 我正在处理的分支。

    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

        var tableView = UITableView()

        if self.searchPredicate == nil {
            tableView = self.tableView
        } else {
            tableView = (self.searchController.searchResultsUpdater as! MasterViewController).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)")
            // TODO: need fix this

            // ORIGINAL CODE
            // self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code

            // PROSPECTIVE SOLUTION CODE
            println("*** NSFetchedResultsChangeUpdate (object)")
            if searchPredicate == nil {
                self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code
            } else {
                // Should search the do something w/ the UISearchControllerDelegate or UISearchResultsUpdating
                // Instead of "indexPath", it should be "searchIndexPath"--How?
                let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell // My cell is a vanilla cell, not a xib
                let location = controller.objectAtIndexPath(searchIndexPath) as Location // My object is a "Note"
                cell.configureForLocation(location) // This is from the other guy's code, don't think it's applicable to me
            }

        case .Move:
            println("*** NSFetchedResultsChangeMove (object)")
            tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
        default:
            return
        }
    }



    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        if self.searchPredicate == nil {
            self.tableView.endUpdates()
        } else {
            println("controllerDidChangeContent")
            (self.searchController.searchResultsUpdater as! MasterViewController).tableView.endUpdates()
        }

    }

编辑:根据@pbasdf,我正在添加 TableView 方法。

// MARK: - Table View

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return self.fetchedResultsController.sections?.count ?? 0
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if self.searchPredicate == nil {
        let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
        return sectionInfo.numberOfObjects
    }

    let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
    return sectionInfo.numberOfObjects
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
    self.configureCell(cell, atIndexPath: indexPath)
    return cell
}

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    // Return false if you do not want the specified item to be editable.
    return true
}

问题在于您的 tableView 数据源方法:numberOfSectionsInTableview:tableView:numberOfRowsInSection:tableView:cellForRowAtIndexPath:。如果 searchPredicate 不为 nil,您需要这些方法中的每一个来 return 不同的结果——就像您的 tableView:commitEditingStyle: 方法一样。我会让 filteredObjects 成为实例 属性(在 class 的开头定义),以便所有这些方法都可以访问它:

var filteredObjects : [Note]? = nil

现在,当搜索文本发生变化时,您想要重建 filteredObjects 数组。所以在updateSearchResultsForSearchController中添加一行根据新的谓词重新计算它:

    if let searchText = searchText {
        searchPredicate = NSPredicate(format: "noteBody contains[c] %@", searchText)
        filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
            return self.searchPredicate!.evaluateWithObject([=11=])
        } as [Note]?
        self.tableView.reloadData()
        println(searchPredicate)
    }

我还建议(为简单起见)当您激活搜索时,结果将全部显示在一个部分中(否则您必须计算出您的过滤结果属于多少部分 - 可能但很烦人):

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    if searchPredicate == nil {
        return self.fetchedResultsController.sections?.count ?? 0
    } else {
        return 1
    }
}

接下来,如果 searchPredicate 不为 nil,则该部分中的行数将为 filteredObjects 的计数:

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 {
        return filteredObjects?.count ?? 0
    }
}

最后,如果 searchPredicate 不为 nil,则需要使用 filteredObjects 数据而不是 fetchedResultsController 来构建单元格:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
    if searchPredicate == nil {
        self.configureCell(cell, atIndexPath: indexPath)
        return cell
    } else {
        // configure the cell based on filteredObjects data
        ...
        return cell
    }
}

不确定你的单元格中有什么标签等,所以我把它留给你来排序。