I'm trying to add a SearchBar to a TableView, which is storing its data with CoreData. But when I try to search the TableView isn't updating. I think that the fetch is working, but table is not being updated.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
return
}
else {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let sRequest: NSFetchRequest<Note> = Note.fetchRequest()
sRequest.predicate = NSPredicate(format: "SELF.title CONTAINS[cd] %#", searchText)
sRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: sRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
print(sRequest)
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to fetch entities: \(error)")
}
self.mainTable.reloadData()
}
return
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = mainTable.dequeueReusableCell(withIdentifier: "prototypeCell")!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tableRequest: NSFetchRequest<Note> = Note.fetchRequest()
tableRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
var fetchedResultsController = NSFetchedResultsController(fetchRequest: tableRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to fetch entities: \(error)")
}
let noteCell = fetchedResultsController.object(at: indexPath)
cell.textLabel?.text = noteCell.title!
let formatter = DateFormatter()
formatter.dateFormat = "d.M.y, HH:mm"
let dateString = formatter.string(from: noteCell.date! as Date)
cell.detailTextLabel?.text = dateString
return cell
}
The problem is this line:
let fetchedResultsController = NSFetchedResultsController(...
let means that this is a local variable. So you create a new fetched results controller and then throw it away.
Meanwhile, in your cellForRowAt, you refer to something else called fetchedResultsController:
var fetchedResultsController = ...
These are two completely different objects.
The fact is that both are wrong. You should not be doing a fetch every time your table view data source is called upon to consider a row. Look at how the Xcode app templates work! There is one fetched results controller, permanently. It does its fetch once and then just sits there holding the data. That is what you should be doing.
Related
I am trying to use NSFetchedResultsController to fetch records from CoreData
I am using the following code:
let fetchedResultsController: NSFetchedResultsController<Appointment> = {
let fetchRequest = NSFetchRequest<Appointment>(entityName: "Appointment")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
let moc:NSManagedObjectContext = CoreDataManager.managedObjectContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
return frc
}()
And in numberOfItemsInSection delegate I have defined
if let count = fetchedResultsController.sections?[0].numberOfObjects {
return count
}
return 0
Last in cellForItemAt delegate method, I have defined
let appointment = self.fetchedResultsController.object(at: indexPath)
The issue, is it does not display any records from core data. Nor does it show any error. the list just comes empty.
If I fetch the record using the following code it works.
public func fetchAppointmentList() -> [Appointment] {
var list:[Appointment] = []
let request: NSFetchRequest<Appointment> = Appointment.fetchRequest()
do {
list = try moc.fetch(request)
} catch {
fatalError("\(error)")
}
return list
}
override func viewDidLoad() {
super.viewDidLoad()
self.appointmentList = self.fetchAppointmentList()
}
// Removed rest of the code to keep it simple.
Why is my fetchedResultsController not working?
Thanks.
Okay, I forgot to call performFetch() in viewDidLoad(), defining the following code in viewDidLoad solved my issue.
do {
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
I'm refactoring an existing project from Swift 2 to Swift 3. Everything has been straightforward until I got to refactoring Core Data. I'm able to create managed objects and persist them in the managedObjectContext, but I'm having difficulty getting NSFetchedResultsController to work. I took a look at this post, but it's not getting me across the finish line.
After importing records from a JSON, I verify there are objects in my managedObjectContext with the following code:
func recordCount() -> Int {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "MyEntity")
let count = try! context.count(for: fetchRequest)
return count
}
When I create a fetchedResultsController, I'm running into trouble. My code doesn't crash, but it doesn't return NSManagedObjects despite there being objects that match my search.
Here's how I'm creating my NSFetchedResultsController in a UIViewController.
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
// This is set on a prior viewController before segue.
// I've verified it's not nil
var selectedEquipmentString: String?
let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
lazy var fetchedResultsController: NSFetchedResultsController<MyEntity> = {
// I've tried altering the syntax of the fetchRequest
// let fetchRequest: NSFetchRequest<MyEntity> = MyEntity.fetchRequest()
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "MyEntity")
let sortDescriptor = NSSortDescriptor(key: "generalArea", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = NSPredicate(format: "equipmentDescription == %#", self.selectedEquipmentString!)
let frc: NSFetchedResultsController<MyEntity> = NSFetchedResultsController(fetchRequest: fetchRequest as! NSFetchRequest<MyEntity>, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "generalArea", cacheName: nil)
frc.delegate = self
return frc
}()
// MARK: - View Lifecycle Methods (abbreviated)
override func viewDidLoad() {
super.viewDidLoad()
// I've tried moving this call to viewWillAppear and viewDidAppear without success
fetchObjectsFromManagedObjectContext()
}
// MARK: - Core Data Methods (abbreviated)
func fetchObjectsFromManagedObjectContext() {
do {
try fetchedResultsController.performFetch()
} catch {
print("error: \(error)")
return
}
print ("There are \(fetchedResultsController.fetchedObjects!.count) returned from fetchObjectsFromManagedObjectContext")
}
}
This code doesn't crash, but it doesn't return any records from a fetchRequest. I was able to force a crash with a typo in the predicate, but without a typo there are no objects returned despite objects that match the predicate.
I welcome any suggestions re: where my mistake is. I rest assured knowing it will be a startlingly silly oversight on my part. Thank you for reading.
Your NSFetchRequest should have a type NSFetchRequest<MyEntity>, but you specify NSFetchRequest<NSFetchRequestResult>. Try changing this and let me know if it helps or not
please check below code for NSFetchedResultsController swift 3..
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
// MARK: - NSFetchedResultsController
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<UserExistenceOnXMPPCD> = {
let fetchRequest = NSFetchRequest<UserExistenceOnXMPPCD>(entityName: "UserExistenceOnXMPPCD")
fetchRequest.sortDescriptors = [
NSSortDescriptor(key: "name", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:CoreDataController.sharedInstance.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
try! fetchedResultsController.performFetch()
fetchedResultsController.delegate = self
if let quotes = fetchedResultsController.fetchedObjects {
if quotes.count > 0 {
print(quotes.count)
}
}
return fetchedResultsController
}()
// MARK: - NSFetchedResultsController delegate methods
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
}
I never imagined it'd be so hard to reorder a table and save the new order to Core Data (and perhaps I'm overthinking it). The bit of code below is throwing me the following error: "The number of rows contained in an existing section after the update must be equal to the number of rows contained in that section before the update."
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
initializeFetchedResultsController()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context2: NSManagedObjectContext = appDel.managedObjectContext
let request2 = NSFetchRequest(entityName: "Activities")
let activityOrderSort = NSSortDescriptor(key: "activityOrder", ascending: true)
request2.sortDescriptors = [activityOrderSort]
let predicate = NSPredicate(format: "date == %#", date)
request2.predicate = predicate
var fetchResults2: [NSManagedObject]
do {
try fetchResults2 = (appDel.managedObjectContext.executeFetchRequest(request2) as! [NSManagedObject])
if fromIndexPath.row > toIndexPath.row {
for i in toIndexPath.row..<fromIndexPath.row {
fetchResults2[i].setValue(i+1, forKey: "activityOrder")
}
fetchResults2[fromIndexPath.row].setValue(toIndexPath.row, forKey: "activityOrder")
}
if fromIndexPath.row < toIndexPath.row {
for i in fromIndexPath.row + 1...toIndexPath.row {
fetchResults2[i].setValue(i-1, forKey: "activityOrder")
}
fetchResults2[fromIndexPath.row].setValue(toIndexPath.row, forKey: "activityOrder")
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
do {
try appDel.managedObjectContext.save()
} catch let error as NSError {
print("Saving error: \(error.localizedDescription)")
}
initializeFetchedResultsController()
}
Here is the initializeFetchedResultsController() code for reference:
func initializeFetchedResultsController() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
context = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Activities")
let orderSort = NSSortDescriptor(key: "activityOrder", ascending: true)
fetchRequest.sortDescriptors = [orderSort]
let predicate = NSPredicate(format: "date == %#", date)
fetchRequest.predicate = predicate
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: self.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
I tried to pull as much as I could from the answer from a previous post here: Save new order to core data after the using the tableView:moveRowAtIndexPath:toIndexPath: method. Also, I have the canEditRowAtIndexPath function set up and it appears to be functioning properly. Is there anything obvious that I'm messing up in the provided code? Is there an easier solution? Thanks for the support.
I'm using the NSFetchedResultsController, to populate a UITableView, i'm trying to add a category filter, so if the user choose a category a need to reload the data on the UITableView, this is what i'm trying to do.
var categoriaAtual : Int?
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
print("Already fetch")
return _fetchedResultsController!
} else {
print("New Fetch")
}
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
self.managedObjectContext = managedContext
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Frases", 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: "favorita", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchLimit = 20
if categoriaAtual != nil {
print("Categoria Atual \(categoriaAtual)")
fetchRequest.predicate = NSPredicate(format: "categoria = %d",categoriaAtual!)
fetchRequest.fetchLimit = 2
} else {
print("No predicate");
}
//
// 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: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// 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.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func setarCategoria(cat: Int) {
categoriaAtual = cat
_fetchedResultsController = nil
self.tableView.reloadData()
}
Now i have a slide out menu called MenuTableView.swift,i make the call like this: ( I Think the problem is here )
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeVC = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeTableViewController
homeVC.setarCategoria(indexPath.row)
self.slideMenuController()?.closeLeft()
}
I do get the print saying that this is a new fetch, but the TableView does not change at all.
instantiateViewControllerWithIdentifer creates a new instance of the viewController (and therefore a new instance of the fetchedResultsController), which is how you get the "New Fetch" log but your original table view doesn't change. You probably just need to give MenuTableView a delegate that your HomeViewController can implement.
the fetchedresultController does not return my custom nsmanagedObject in prepareForSegue.
When I add a new entry in my tableView (the "+" button that calls "insertNewObject"), the correct name, from my custom object, appears, with the correct text. But when I click on the line to perform the segue, there is a crash, exc breakpoint, I click on continue, and the program continues without adding more information.
The code to cast the nsmanagedObject as a Section class :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
println("idx: \(indexPath) , objet : \(self.fetchedResultsController.objectAtIndexPath(indexPath))") //outputs ""
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as Section//NSManagedObject
let controller = (segue.destinationViewController as UINavigationController).topViewController as DetailViewController
//controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Section", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(key: "name", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func insertNewObject(sender: AnyObject) { //this works
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as Section
newManagedObject.name = String("insert")
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let section = self.fetchedResultsController.objectAtIndexPath(indexPath) as Section
cell.textLabel.text = section.name;//this works
}