本文介绍了Swift UISearchController在Core Data Project中连接,应用程序运行,但搜索不更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

过去几天,我在一个Swift项目中为使用Core Data的 TableViewController 创建了一个过滤器。我终于知道我需要使用 UISearchController ,为 searchController创建一个 NSPredicate 。 searchBar 等。

I have been wrestling the past few days with creating a filter in a Swift project for a TableViewController that uses Core Data. I finally figured out I need to use a UISearchController, create an NSPredicate for the searchController.searchBar, etc.

我发现,但是在这个项目之后建模我的 TVC 所有的灯都亮了,但没人在家。我可以搜索,谓词创建在 searchBar ,添加,删除等,但单元格不更新搜索。我错过了什么,但我不知道什么。

I found this post EXTREMELY helpful, but after modeling my TVC after this project, I find "all the lights are on, but nobody's home". I can search, predicate is created in searchBar, add, remove, etc. but the cells don't update for the search. I'm missing something, but I don't know what.

这是代码的相关部分。

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

我认为我的问题住在这里本节。自动填充是我的朋友到这里,但我没有看到自动完成中引用的 searchIndex 我想我错过了一些东西,但我不知道是什么或如何。

I think my problem "lives" here in this section. Autocomplete has been my friend up to here, but I'm don't see "searchIndex" referenced in AutoComplete. I think I'm missing something, but I'm not sure what or how.

阅读。 。

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

    }

编辑:Per @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,那么你需要每个方法返回不同的结果 - 很像你的 tableView:commitEditingStyle:方法。我会让 filteredObjects 一个实例属性(在类的开头定义),以便所有这些方法可以访问它:

The problems lie in your tableView datasource methods: numberOfSectionsInTableview:, tableView:numberOfRowsInSection:, and tableView:cellForRowAtIndexPath:. You need each of those methods to return different results if the searchPredicate is not nil - much like your tableView:commitEditingStyle: method does. I would make filteredObjects an instance property (defined at the start of the class) so that all those methods can access it:

var filteredObjects : [Note]? = nil



现在,当搜索文本更改时,要重新创建filteredObjects数组。因此,在 updateSearchResultsForSearchController 中,添加一行以根据新的谓词重新计算它:

Now, when the search text changes, you want to rebuild the filteredObjects array. So in updateSearchResultsForSearchController, add a line to recompute it based on the new predicate:

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

我还建议(为了简单起见),当您激活搜索时,结果显示在一个部分(否则你必须计算过滤的结果分为多少部分 - 可能,但令人讨厌):

I would also recommend (for simplicity) that when you activate the search, the results are displayed all in one section (otherwise you have to work out how many sections your filtered results fall into - possible but tiresome):

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

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

Next, if the searchPredicate is not nil, the number of rows in the section will be the count of 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:

Finally, if the searchPredicate is not nil, you need to build the cell using the filteredObjects data, rather than the 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
    }
}

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

Not sure what labels etc you have in your cells, so I leave it to you to sort that bit.

这篇关于Swift UISearchController在Core Data Project中连接,应用程序运行,但搜索不更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-29 22:07