Reputation: 85
I am stumped with implementing the NSFetchedResultsController as I keep getting errors. I am trying to connect Core Data with the UITableView, and I am not getting to the next step by having UITableView draw out the correct number of rows according to the number of records from Core Data. I get this error message:
*** Terminating app
due to uncaught exception 'NSRangeException', reason: '***
[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
Here's my code below:
import UIKit
import CoreData
class CustomerProfileViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var coreDataStack: CoreDataStack!
var itemList: [NSManagedObject] = [] // I hope to have the fetch results controller manage this object
// The data model contains one entity "Item" with one attribute "name"
lazy var fetchResultsControllerItem: NSFetchedResultsController<Item> = {
// Initialize Fetch Request
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
// Initialize Fetch Results Controller
let fetchResultsControllerItem =
NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: self.coreDataStack.managedContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchResultsControllerItem.delegate = self
return fetchResultsControllerItem
}()
override func viewDidLoad() {
super.viewDidLoad()
do {
try fetchResultsControllerItem.performFetch()
print("Fetch worked! 🐺")
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
let cellNib = UINib(nibName: "SummaryCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "SummaryCell")
let cellNib = UINib(nibName: "ListingsCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "ListingsCell")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2 // Corresponds to the custom table view cells as Nib files
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "SUMMARY"
} else if section == 1 {
return "ITEM LISTINGS"
} else {
return ""
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
/* Adding the "guard let sections" to the "return
sectionInfo.numberOfObject" below causes an error: *** Terminating app
due to uncaught exception 'NSRangeException', reason: '***
[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]' */
enter code here
guard let sections = fetchResultsControllerItem.sections else {
return 0
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SummaryCell", for: indexPath) as! SummaryCell
cell.totalNumberOfItems?.text = "\(itemList.count)"
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListingsCell", for: indexPath) as! ListingsCell
let listItem = fetchResultsControllerItem.object(at: indexPath)
cell.itemName?.text = listItem.name
return cell
}
return UITableViewCell()
}
}
Below displays the file containing the CoreDataStack implementation:
import Foundation
import CoreData
class CoreDataStack {
private let modelName: String
init(modelName: String) {
self.modelName = modelName
}
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext() {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
}
I did use Liya to confirm that there are Items with names on record after examining the sqlite file. To whomever having any tips or ideas, I would appreciate any help. Thanks.
Upvotes: 2
Views: 477
Reputation: 21536
The problem is that, in the FetchedResultsController’s view if the world, there is only one section: section 0. But your tableView has two sections, and you want the FRC to provide the data for section 1. So you need to remap the FRC indexPaths to the correct section in the table view, in the tableView datasource methods. So in numberOfRowsInSection
, your code is currently asking the FRC for the number of objects in section 1, and the FRC throws the error because, as far as it is concerned, there is no section 1. Just replace:
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
with:
let sectionInfo = sections[0]
return sectionInfo.numberOfObjects
in that method. Then in cellForRowAt
, replace this:
let cell = tableView.dequeueReusableCell(withIdentifier: "ListingsCell", for: indexPath) as! ListingsCell
let listItem = fetchResultsControllerItem.object(at: indexPath)
cell.itemName?.text = listItem.name
return cell
with this:
let cell = tableView.dequeueReusableCell(withIdentifier: "ListingsCell", for: indexPath) as! ListingsCell
let listItem = fetchResultsControllerItem.fetchedObjects![indexPath.row]
cell.itemName?.text = listItem.name
return cell
(This uses the fetchedObjects
property of the FRC to avoid using the wrong section in the indexPath in object(at:)
).
Upvotes: 1