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()
}
Related
This is my first Swift project also with CoreData. Swift doesn't have return instancetype like in Objective C.
In Swift - CoreData class, I am using common base class for NSManagedObject. There I have implemented a class function.
class func FRCWith(sortArray: [NSSortDescriptor], predicate: NSPredicate? = nil, mainContext: NSManagedObjectContext = CoreDataManager.shared.managedObjectContext) -> NSFetchedResultsController<NSManagedObject> {
let className = self.nameOfTheClass
let fetchRequest: NSFetchRequest<NSManagedObject> = NSFetchRequest.init(entityName: className)
fetchRequest.sortDescriptors = sortArray
fetchRequest.fetchBatchSize = 4
if let predicateValue = predicate {
fetchRequest.predicate = predicateValue
}
let fetchedResultsController: NSFetchedResultsController<NSManagedObject> = NSFetchedResultsController.init(fetchRequest: fetchRequest, managedObjectContext: mainContext, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
print(error.localizedDescription)
}
return fetchedResultsController
}
I want to return NSFetchedResultsController<customCoreDataClass> instead of common NSFetchedResultsController<NSManagedObject>.
var schoolvalue: NSFetchedResultsController<SchoolDetails> = SchoolDetails.FRCWith([sort])
var schoolvalue: NSFetchedResultsController<StudentDetails> = StudentDetails.FRCWith([sort], preidcate)
Right now I am using in for loop like this and checking is that FRC as! customClass
let loopValue: SchoolDetails = someFRC.object(at: IndexPath.init(row: value, section: 0)) as! SchoolDetails
Found some kind of solution in StackOverflow. That T thing, I do not understand that.
Is that possible make a method like that?
Thanks, #Paulw11 for an answer.
Randomly app crash when init
let fetchedResultsController: NSFetchedResultsController<T> = NSFetchedResultsController<T>(fetchRequest: fetchRequest, managedObjectContext: mainContext, sectionNameKeyPath: nil, cacheName: nil)
from below answer.
Because
let fetchRequest:NSFetchRequest<T> = T.fetchRequest() as! NSFetchRequest<T>\\ line return nil. There is no crash below iOS 10.x.
It only occurs when downloading from App store or Testflight.
You can use Swift generics for this. You specify a placeholder, often T is used but it doesn't have to be, that "stands in" for the actual class that will be used. In this case you need to specify that T must be a NSManagedObject or a subclass of NSManagedObect
Declare your function as:
class func FRCWith<T: NSManagedObject>(sortArray: [NSSortDescriptor], predicate: NSPredicate? = nil, mainContext: NSManagedObjectContext) throws -> NSFetchedResultsController<T> {
let fetchRequest:NSFetchRequest<T> = T.fetchRequest() as! NSFetchRequest<T>
fetchRequest.sortDescriptors = sortArray
fetchRequest.fetchBatchSize = 4
fetchRequest.predicate = predicate
let fetchedResultsController: NSFetchedResultsController<T> =
NSFetchedResultsController<T>(fetchRequest: fetchRequest, managedObjectContext: mainContext, sectionNameKeyPath: nil, cacheName: nil)
try fetchedResultsController.performFetch()
return fetchedResultsController
}
Then you can invoke it using:
if let frc: NSFetchedResultsController<SchoolDetails> = try? CD.FRCWith(sortArray: [], mainContext: moc) {
// Do something with results
if let schoolDetails = frc.fetchedObjects?.first {
// schoolDetails will be an instance of SchoolDetails
}
}
Note that I have declared the function as throws rather than hiding the error inside the function.
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 used below code fetch the list of Article from core data and it is giving me the expected result.
let moc = CoreDataHelper().backgroundContext
let employeesFetch = NSFetchRequest<NSFetchRequestResult>(entityName:"Article")
do {
let fetchedEmployees = try moc?.fetch(employeesFetch) as! [Article]
print(fetchedEmployees.count)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
Output : 376
But when I am trying to fetch with NSFetchedResultsController. The fetchedObjects always returning nil. I used below code.
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Article> = {
// Create Fetch Request
let moc = CoreDataHelper().backgroundContext
// let fetchRequest: NSFetchRequest<Article> = Article.fetchRequest()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Article")
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key:"category", ascending: true)]
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:moc!, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
let quotes = fetchedResultsController.fetchedObjects
return fetchedResultsController as! NSFetchedResultsController<Article>
}()
The quotes is always nil. I am not able to figure out what is the problem?
try calling performFetch() on fetchedResultsController outside the closure.
hope that will work.
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.