在Swift中搜索核心数据

时间:2017-05-06 19:15:31

标签: swift search core-data fetch

我尝试使用核心数据搜索标准的主详细信息模板。

搜索栏正在运行,它会更改搜索模板(我将字符串转换为日期间隔),但是tableView.reloadData() 没有更新表格。似乎fetchedResultsController永远不会更新。

以下是代码:

//  MasterViewController.swift
//  CoreData example

import UIKit
import CoreData

class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {

var detailViewController: DetailViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil

//--- Added to start Master-Detail project with Core Data
var taskPredicate: NSPredicate?
var searchtemplate: String? {didSet {print (searchtemplate as Any)}}

let searchController = UISearchController(searchResultsController: nil)

func filterContentForSearchText(_ searchText: String, scope: String = "All") {
    searchtemplate = searchText
    tableView.reloadData()
}
//---//

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    navigationItem.leftBarButtonItem = editButtonItem

    let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
    navigationItem.rightBarButtonItem = addButton
    if let split = splitViewController {
        let controllers = split.viewControllers
        detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
    }

    // Setup the Search Controller
    searchController.searchResultsUpdater = self
    searchController.searchBar.delegate = self
    definesPresentationContext = true
    searchController.dimsBackgroundDuringPresentation = false

    // Setup the Scope Bar
    searchController.searchBar.scopeButtonTitles = ["All", "...", "...", "..."]
    tableView.tableHeaderView = searchController.searchBar
}

override func viewWillAppear(_ animated: Bool) {
    clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
    super.viewWillAppear(animated)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func insertNewObject(_ sender: Any) {
    let context = self.fetchedResultsController.managedObjectContext
    let newEvent = Event(context: context)

    // If appropriate, configure the new managed object.
    newEvent.timestamp = NSDate()

    // Save the context.
    do {
        try context.save()
    } catch {
        // Replace this implementation with code to handle the error appropriately.
        // fatalError() 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.
        let nserror = error as NSError
        fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
}

// MARK: - Segues

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail" {
        if let indexPath = tableView.indexPathForSelectedRow {
        let object = fetchedResultsController.object(at: indexPath)
            let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
            controller.detailItem = object
            controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
            controller.navigationItem.leftItemsSupplementBackButton = true
        }
    }
}

// MARK: - Table View

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

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let sectionInfo = fetchedResultsController.sections![section]
    return sectionInfo.numberOfObjects
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    let event = fetchedResultsController.object(at: indexPath)
    configureCell(cell, withEvent: event)
    return cell
}

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

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let context = fetchedResultsController.managedObjectContext
        context.delete(fetchedResultsController.object(at: indexPath))

        do {
            try context.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() 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.
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}

func configureCell(_ cell: UITableViewCell, withEvent event: Event) {
    cell.textLabel!.text = event.timestamp!.description
}

// MARK: - Fetched results controller

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

    let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()

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

    // Edit the sort key as appropriate.
    let sortDescriptor = NSSortDescriptor(key: "timestamp", ascending: false)

    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

//--- Added to start Master-Detail project with Core Data

    // Filtering results with Predicate
    if let aSearchtemplate = searchtemplate {
        let timeInterval = Double(aSearchtemplate)
        if timeInterval != nil {
            let time = Date().addingTimeInterval(-timeInterval!)
            taskPredicate = NSPredicate(format: "Events.timestamp > %@", time as NSDate)
            _fetchedResultsController?.fetchRequest.predicate = taskPredicate
            }
        }

//---//

    do {
        try _fetchedResultsController!.performFetch()
    } catch {
         // Replace this implementation with code to handle the error appropriately.
         // fatalError() 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. 
         let nserror = error as NSError
         fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }

    return _fetchedResultsController!
}    
var _fetchedResultsController: NSFetchedResultsController<Event>? = nil

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    switch type {
        case .insert:
            tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
        case .delete:
            tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
        default:
            return
    }
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
            configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
        case .move:
            configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
            tableView.moveRow(at: indexPath!, to: newIndexPath!)
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.endUpdates()
}

/*
 // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed.

 func controllerDidChangeContent(controller: NSFetchedResultsController) {
     // In the simplest, most efficient, case, reload the table view.
     tableView.reloadData()
 }
 */

}

//--- Added to start Master-Detail project with Core Data
extension MasterViewController: UISearchBarDelegate {
// MARK: - UISearchBar Delegate
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
    filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}

extension MasterViewController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
    let searchBar = searchController.searchBar
    let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
    filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
}

//---//

2 个答案:

答案 0 :(得分:1)

您还没有做任何事情要求fetchedResultsController获取新数据。你在吸气器中做的第一件事是:

reloadData()

当调用tableView:cellForRowAtIndexPath:时,框架会调用fetchedResultsController,要求Uncaught Error: [$injector:modulerr] Failed to instantiate module AdminPortal due to: Error: Cannot combine: component|bindings|componentProvider with: templateProvider|templateUrl|template|notify|async|controller|controllerProvider|controllerAs|resolveAs in stateview: '$default@auth.portal.dashboard' ,然后返回您之前获得的相同获取结果。

当您更改搜索栏文字时,您将不得不做一些事情来更改由tableview的数据源返回的项目集。

答案 1 :(得分:0)

不要在fetchedResultsController的声明闭包中使用逻辑来更改谓词。声明谓词属性

var fetchPredicate : NSPredicate? {
    didSet {
        fetchedResultsController.fetchRequest.predicate = fetchPredicate
    }
}

并且在没有丑陋的 objectivecish 支持实例变量的情况下以Swift方式懒惰地声明fetchedResultsController

lazy var fetchedResultsController: NSFetchedResultsController<Event> = {

    let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
    fetchRequest.predicate = self.fetchPredicate

    fetchRequest.fetchBatchSize = 20

    let sortDescriptor = NSSortDescriptor(key: "timestamp", ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]

    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
    aFetchedResultsController.delegate = self

    do {
        try aFetchedResultsController.performFetch()
    } catch let error as NSError{
        fatalError("Unresolved error \(error), \(error.userInfo)")
    }

    return aFetchedResultsController
}()

然后根据搜索/不搜索设置fetchPredicate,执行提取并重新加载表。