2017-05-06 103 views
0

我正在嘗試使用Core Data實現對標準Master-Detail模板的搜索。在Swift中搜索核心數據

搜索欄正在工作,它更改搜索模板(我將字符串轉換爲日期間隔),但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) 
} 
} 

//---// 

回答

0

我找到了!

如果您離開緩存,TableView將不會刷新。因此,在每個新的謂詞要應用之前,您必須這樣做:NSFetchedResultsController.deleteCache(withName:nil)

如果您使用多個,應將'nil'替換爲您的緩存文件名。

而你必須解決這個功能的類本身。不是這個例子。

所以,感謝Vadian,我們只能更新這一部分:

var fetchPredicate : NSPredicate? { 
    didSet { 
     NSFetchedResultsController<NSFetchRequestResult>.deleteCache(withName: nil) 
     fetchedResultsController.fetchRequest.predicate = fetchPredicate 
    } 
} 

謝謝Vadian,謝謝你,斯科特,你的投入!

0

不要把邏輯改變謂詞的fetchedResultsController聲明關閉。聲明一個謂詞屬性

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

並在夫特方式懶惰地聲明fetchedResultsController沒有難看objectivecish背襯的實例變量:

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取決於搜索/不進行搜索,執行提取和重新加載表格。

+0

無法將其更改爲「懶惰」,因爲「懶惰可能不會用於計算屬性」。 –

+0

懶惰的變種,謝謝,但我仍然不明白如何觸發新的獲取。我已通過添加取表演和表更新更新搜索控制器功能,但沒有結果: 做{ 嘗試fetchedResultsController.performFetch() }趕上讓誤差NSError { fatalError(「未解決的錯誤\(錯誤) \(error.userInfo)「) } } tableView.reloadData() } –

+0

將控制器的聲明保持原樣。目前,您將狀態更改爲*搜索*或*不搜索*將謂詞設置爲「nil」或相應的搜索謂詞,然後調用「performFetch()」和「reloadData()」(** not ** 'fetchedResultsController'閉包)。 – vadian

1

您還沒有做任何事情要求fetchedResultsController獲取新數據。您在吸氣做的第一件事是:

if _fetchedResultsController != nil { 
    return _fetchedResultsController! 
} 

reloadData()被調用時,框架調用tableView:cellForRowAtIndexPath:,詢問了fetchedResultsController,剛回來你以前有相同的取結果。

當你改變你的搜索欄文本時,你將不得不做一些事情來改變你的tableview的數據源返回的一組項目。