Core Data: Showing NSFetchRequestResult Search Results with UITableView - swift

I've been playing Core Data for the past several days. I'm fetching data with NSFetchedResultsController. My entity has such attributes as age (Int), firstName (String), lastName (String), uuid (String). I am able to insert a new record to the database. I am able to delete a record. I am also able to edit a record. What I cannot do is to show a search result with the table.
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - Instance variables
private let persistentContainer = NSPersistentContainer(name: "Profiles") // core data model file (.xcdatamodeld)
var managedObjectContext: NSManagedObjectContext?
// MARK: - IBOutlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchButton: UIButton!
// MARK: - IBActions
#IBAction func searchTapped(_ sender: UIButton) {
searchRecords()
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// loading persistentContainer //
persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
if let error = error {
print("Unable to Load Persistent Store")
print("\(error), \(error.localizedDescription)")
} else {
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
}
}
// MARK: - Life cycle
// MARK: - fetchedResultsController(controller with the entity)
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Person> = {
// Create Fetch Request with Entity
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
// MARK: - fetchedResultsController
// MARK: - TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let people = fetchedResultsController.fetchedObjects else { return 0 }
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ProfileTableViewCell
let person = fetchedResultsController.object(at: indexPath)
// Configure Cell
cell.firstLabel.text = person.firstName
cell.lastLabel.text = person.lastName
cell.ageLabel.text = String(person.age)
return cell
}
// MARK: - TableView
// MARK: - Showing search result
func searchRecords() {
let context = self.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let predicate = NSPredicate(format: "firstName CONTAINS[c] %#", "Sandra")
fetchRequest.predicate = predicate
do {
try fetchedResultsController.performFetch()
/*
let result = try context.fetch(fetchRequest)
if (result.count > 0) {
print(result.count)
}
*/
} catch {
print("bad")
}
}
// MARK: - Showing search result
}
extension HomeViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break;
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break;
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .automatic)
}
break;
default:
tableView.reloadData()
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
}
}
If I run searchRecords(), result.count will correctly return the number of records. But the table stays the same. So how can I show my search result with the table? Thanks.

Replace your tableview datasource with
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = self.fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
Hope it is helpful

You've got more experience than I do, but the last time I used CoreData and a collectionView I needed to force cast the result like this:
do {
return try context.fetch(request) as? [Friend]
} catch let err {
print(err)
}
Then I was able to pull the info to the table more easily.

Related

Swift: UITableViewController selecting cell & passing fetched objects to UIViewController

I’m trying to make a simple note app using Core Data, but I’m running into problem with fetchedResultsController.object(at: indexPath) & passing the data onto my View Controller. using prepare(for segue: UIStoryboardSegue, sender: Any?).
My Core Data Entity is named Notes with the following Attributes
dateStamp Integer 64
noteName String
noteImage Data
noteDescription String
However in order to understand the problem I’ve made separate project & limited it to just the noteName & dateStamp.
So there are three Controllers & one Helper file
MasterViewController
AddViewController
DetailViewController
EditViewController
dateHelper
The MasterView is my UITableController with my NSFetchedResultsControllerDelegate, so the code is as follows….
import UIKit
import CoreData
class MasterViewController: UITableViewController {
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var fetchedResultsController: NSFetchedResultsController<Notes>!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
fetchFromCoreData()
navigationItem.leftBarButtonItem = editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
}
#objc
func insertNewObject(_ sender: Any) {
let addController = storyboard?.instantiateViewController(withIdentifier: "addNotes")
as! UINavigationController
self.present(addController, animated: true, completion: nil)
}
private func configureCells(cell: noterViewCell, withEvent note: Notes, indexPath: IndexPath) {
let record = fetchedResultsController.object(at: indexPath)
cell.noteName.text = record.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: record.dateStamp))
if let noteName = record.value(forKey: "noteName") as? String, let dateTime = record.value(forKey: "dateStamp") as? Int64 {
cell.noteName.text = noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: dateTime))
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = self.fetchedResultsController?.sections else {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "noteCell", for: indexPath) as! noterViewCell
guard let object = self.fetchedResultsController?.object(at: indexPath) else {
fatalError("Attempt to configure cell without a managed object")
}
configureCells(cell: cell, withEvent: object, indexPath: indexPath)
cell.selectbutton.addTarget(self, action: #selector(selectNote(_:)), for: .touchUpInside)
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = fetchedResultsController.managedObjectContext
context.delete(fetchedResultsController.object(at: indexPath))
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func fetchFromCoreData() {
let fetchRequest: NSFetchRequest<Notes> = Notes.fetchRequest()
fetchRequest.fetchBatchSize = 800
let creationDateSortDescriptor = NSSortDescriptor(key: "dateStamp", ascending: false)
//let nameSortDescriptor = NSSortDescriptor(key: "noteName", ascending: false)
fetchRequest.sortDescriptors = [creationDateSortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: "dateStamp", cacheName: nil)
aFetchedResultsController.delegate = self
fetchedResultsController = aFetchedResultsController
do {
try fetchedResultsController.performFetch()
self.tableView.reloadData()
} 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.localizedDescription), \(nserror.localizedFailureReason ?? "could not retrieve")")
//print("Could not save note to CoreData: \(error.localizedDescription)")
}
}
}
extension MasterViewController: NSFetchedResultsControllerDelegate {
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:
tableView.reloadRows(at: [newIndexPath!], with: .fade)
if let updateIndexPath = newIndexPath {
let cell = self.tableView.cellForRow(at: updateIndexPath) as! noterViewCell
let event = anObject as! Notes
cell.dateLabel.text = event.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
}
case .move:
configureCells(cell: tableView.cellForRow(at: indexPath!) as! noterViewCell, withEvent: anObject as! Notes, indexPath: indexPath!)
tableView.moveRow(at: indexPath!, to: newIndexPath!)
#unknown default:
fatalError("Unresolved error")
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
My AddViewController
import UIKit
import CoreData
class AddViewController: UIViewController {
#IBOutlet var noteField: UITextField!
#IBOutlet var dateStamp: UILabel!
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
let masterView = MasterViewController()
var isExisting: Bool = false
var note:Notes? = nil
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
var detailItem: Notes? {
didSet {
// Update the view.
configureView()
}
}
func configureView() {
if let detail = detailItem {
if let noteFieldLabel = noteField, let dateTime = dateStamp {
noteFieldLabel.text = detail.noteName
dateTime.text = dateHelper.convertDate(date: Date.init(seconds: detail.dateStamp))
}
}
}
#IBAction func saveImageButtonPressed(_ sender: Any) {
let context = self.managedObjectContext
let newEvent = Notes(context: context!)
newEvent.noteName = noteField.text
newEvent.dateStamp = noteCreationTimeStamp
do {
try context?.save()
} catch {
let error = error as NSError
fatalError("Unresolved error \(error), \(error.localizedDescription)")
}
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController!.pushViewController(masterView, animated: true)
}
}
#IBAction func cancelView(_ sender: AnyObject) {
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true, completion: nil)
}
}
}
Everything is good there my saveImageButtonPressed saves the data to my managedObjectContext and displays onto the UITableview with no problems.
This code for my DetailViewController
import UIKit
import CoreData
class DetailViewController: UIViewController {
#IBOutlet var dateLabel: UILabel!
#IBOutlet var noteField: UILabel!
let masterView = MasterViewController()
var notes: Notes?
var myNotes = [Notes]()
var isExisting: Bool = false
var index = IndexPath()
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var detailItem: Notes? {
didSet {
// Update the view.
configureView()
}
}
func configureView() {
if let detail = detailItem {
if let noteFieldLabel = noteField, let dateStamp = dateLabel {
noteFieldLabel.text = detail.noteName
dateStamp.text = dateHelper.convertDate(date: Date.init(seconds: detail.dateStamp))
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//fetchNote()
configureView()
}
#IBAction func cancelView(_ sender: AnyObject) {
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true, completion: nil)
}
}
//
func fetchNote() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Notes")
do {
myNotes = try managedObjectContext?.fetch(fetchRequest) as! [Notes]
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.localizedDescription,\(nserror.localizedFailureReason ?? "could not retrieve")")
}
}
//
}
So I’m trying to select my cell to pass the saved data to my DetailViewController but it is not selecting. Nothing happens. The fetchNote function in my DetailViewController was just add-on to try out.
This is my code for my prepare(for segue: UIStoryboardSegue, sender: Any?)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetails" {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
let objects = fetchedResultsController.object(at: selectedIndexPath)
controller.detailItem = objects
}
}
}
and my tableView.didSelectRowAt: indexPath
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = self.tableView.cellForRow(at: indexPath) as! noterViewCell
let event = fetchedResultsController.object(at: indexPath)
cell.dateLabel.text = event.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
//configureCells(cell: tableView.cellForRow(at: indexPath!) as! noterViewCell, withEvent: anObject as! Notes, indexPath: indexPath!)
self.performSegue(withIdentifier: "showDetails", sender: self)
}
I added self.performSegue(withIdentifier: "showDetails", sender: self) but the DetailViewController is just blank.
In order to understand further I added button in the cellForRowAt indexPath:…
cell.selectbutton.addTarget(self, action: #selector(selectNote(_:)), for: .touchUpInside)
with the following action
#objc
func selectNote(_ sender: noterViewCell) {
let indexPath = IndexPath()
let objects = fetchedResultsController.object(at: indexPath) as Notes
let controller = DetailViewController()
controller.detailItem = objects
//let detailView = //storyboard?.instantiateViewController(withIdentifier: "viewNotes") as! //UINavigationController
//self.present(detailView, animated: true, completion: nil)
let detailViews = DetailViewController()
self.navigationController?.popToViewController(detailViews, animated: true)
}
and this threw an 'NSInvalidArgumentException', reason: 'no section at index 9223372036854775807' with both methods tried in accessing my Detail View
This was directed exactly on fetchedResultsController.object in my button action #objc MasterViewController_selectNote(_:)
Also listed in the thread was [NSFetchedResultsController objectAtIndexPath:]: scrolling down I found another reason, "cannot access fetched objects before -performFetch:" as well as "NSFetchedResultsController: no object at index %lu in section at index %lu"
I’m calling my fetchFromCoreData() in my viewDidLoad() … I took out the performFetch I pasted it directly in the viewDidLoad(). Still throws the same error if I click on the button. Again blank view selecting using prepareForSegue. I tried using return value in my fetchFromCoreData() function again the same.
I’m not sure if I added to the problem by adding the button but in all cases I’ve attempted to remedy the problem prepareForSegue its still passing a blank view.
I also tried to validate the indexPath using this function
func validateIndexPath(_ indexPath: IndexPath) -> Bool {
if let sections = self.fetchedResultsController.sections,
indexPath.section < sections.count {
if indexPath.row < sections[indexPath.section].numberOfObjects {
return true
}
}
return false
}
again to no avail, calling it on the cellForRowAt indexPath & the app still terminates with the same 'NSInvalidArgumentException’ and the prepareForSegue passing blank view.
Then I added my titleForHeaderInSection in my tableView which is set by month & year that is displaying okay when I save the data in my AddViewController. I hoped it might make a bit of difference but…
func monthDate(string:String?) -> String? {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM"
let dateString = formatter.string(from: date)
let monthDate = formatter.date(from: dateString)
formatter.dateFormat = "MMM, yyyy"
let dateMonth = formatter.string(from: monthDate!)
return dateMonth
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let sectionInfo = fetchedResultsController.sections?[section] else {
return nil
}
return monthDate(string: sectionInfo.name)
}
I really just don't understand where I'm going wrong... I’m using iPhone 11 pro max simulator on IOS14 & Xcode 12.3.(I upgraded about month ago & it did hang on install for about 4 or 5 days, its been bit buggy since)
Although I’m sure I’ve probably missed something quite simple any help will be much appreciated.
On the other hand if I've understood correctly I’m aware NSFetchedResultsController does not like empty sections as per this blog post…
http://www.iosnomad.com/blog/2014/8/6/swift-nsfetchedresultscontroller-trickery
and if that could be the problem, is there simpler solution for Swift 5?
EDIT
In my tableview didSelectRowAt I added
print(fetchedResultsController.object(at: indexPath))
and the console outputs my saved data but it's still not passing it to my DetailViewController.
I tried also in my prepareForSegue adding
print(fetchedResultsController.object(at: selectedIndexPath))
This returned no saved data, as well as returning 0 sections after printing out on the console the numberOfSections in tableView, after selecting my cell. But as I open my app the data is still saved and viewable in my cell and prints out correct section numbers in the console, as I add new note. Only when I select my cell to pass saved data to my DetailViewController the problem occurs.
Problem Solved
I added following variables to my UITableViewController
var cellLabel: [Notes] = []
var selectedLabels = String()
var selectedTime = String()
and added variables to my DetailViewController
var receivedString: String = ""
var recievedTime: String = ""
I changed my code to reflect fetched objects are attached to my selected variables in my didSelectRowAt and equal to my received variables in my prepareForSegue.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let event = fetchedResultsController.object(at: indexPath)
cellLabel = [event]
selectedLabels = event.noteName!
selectedTime = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
print(selectedLabels)
print(selectedTime)
self.performSegue(withIdentifier: "showDetails", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetails" {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.receivedString = selectedLabels
controller.recievedTime = selectedTime
print(selectedLabels)
print(selectedTime)
/*
if let indexPath = self.tableView.indexPathForSelectedRow {
let objects = fetchedResultsController.object(at: indexPath)
controller.noteField.text = objects.noteName
controller.detailItem = objects
}
*/
}
}
and in my viewDidLoad() in DetailViewController
override func viewDidLoad() {
super.viewDidLoad()
noteField.text = receivedString
dateLabel.text = recievedTime
}

How can I fetch core data objects into an array

I am trying to build an application that helps dyslectic children to automate words and I cannot get the lists in my app to work as desired: users can create lists [LijstMO] that contain a set of words [WoordMO]. I am able to create and fetch lists, and to create words, but I can't create an army of words that belong to a specific list. Which superhero can make my day and help me out with a filtered fetch request? All help is extremely appreciated.
First my core data model:
my data model
Then my code:
import UIKit
import CoreData
class WoordTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UITextFieldDelegate {
#IBOutlet weak var woordTextField: UITextField!
var lijst: LijstMO!
var woord: WoordMO!
var woordeninlijst: [WoordMO] = []
var fetchResultController: NSFetchedResultsController<WoordMO>!
override func viewDidLoad() {
super.viewDidLoad()
title = lijst.naam
self.woordTextField.delegate = self
let fetchRequest: NSFetchRequest<WoordMO> = WoordMO.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "naam", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
let context = appDelegate.persistentContainer.viewContext
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do {
try fetchResultController.performFetch()
if let fetchedObjects = fetchResultController.fetchedObjects {
woordeninlijst = fetchedObjects
}
} catch {
print(error)
}
}
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// foutmelding leeg woord
if woordTextField.text == "" {
let alertController = UIAlertController(title: "Oooo Neeeee", message: "Zonder tekst kunnen we dit woord echt niet opslaan. Geef 'm een mooie naam en druk op Bewaar. Ka-tching! ", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "Okidoki", style: .default, handler: nil)
alertController.addAction(alertAction)
present(alertController, animated: true, completion: nil)
return false
}
else {
print("Woord: \(woordTextField.text ?? "")")
// saving the restaurant to database
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
// create new list
woord = WoordMO(context: appDelegate.persistentContainer.viewContext)
woord.naam = woordTextField.text
woord.zitInLijst = lijst
woordTextField.text = ""
appDelegate.saveContext()
print ("WoordMO: \(woord.naam ?? "")")
}
return true
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return woordeninlijst.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let CellIdentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier, for: indexPath) as! WoordTableViewCell
cell.woordLabel.text = woordeninlijst[indexPath.row].naam
return cell
}
// MARK: NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .fade)
}
default:
tableView.reloadData()
}
if let fetchedObjects = controller.fetchedObjects {
woordeninlijst = fetchedObjects as! [WoordMO]
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}

Populate TableView from CoreData with NSFetchRequest

I need to populate my table view with the data from my core data.
I can display my rows but, I can't populate my tableview!
I use Swift 3 and Xcode 8.
This is my code, in this controller I have to display the list.
class iTableViewController: UITableViewController, NSFetchedResultsControllerDelegate{
#IBOutlet var iTableView: UITableView!
override func viewDidLoad(){
super.viewDidLoad()
iTableView.dataSource = self
iTableView.delegate = self
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
let interventsFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Intervento")
do{
//results
let fetchedIntervents = try managerContext.fetch(interventsFetch) as! [Intervento]
for interv in fetchedIntervents{
print(interv.stampRow())
}
NSLog(String(fetchedIntervents.count))
}catch{
fatalError("Failed to fetch employees: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//number of sections
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//numer of rows
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
//print the example cell
cell.textLabel?.text = "title"
cell.detailTextLabel?.text = "subtitle"
return cell
}
//swipe and delete
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if(editingStyle == UITableViewCellEditingStyle.delete){
//TODO
}
}
// click cell
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
performSegue(withIdentifier: "segueDetail", sender: self)
}
override func viewDidAppear(_ animated: Bool){
//iTableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
//iTableView.reloadData()
//fetchData()
}
}
this is my class
extension Intervento {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Intervento> {
return NSFetchRequest<Intervento>(entityName: "Intervento");
}
#NSManaged public var cliente: Int32
#NSManaged public var dataFine: String?
#NSManaged public var dataInizio: String?
#NSManaged public var descrizione: String?
#NSManaged public var foto: String?
#NSManaged public var id: Int32
#NSManaged public var stato: String?
#NSManaged public var tecnico: String?
func stampRow()->String{
let v1 = String(id) + " " + String(cliente) + " "
let v2 = dataInizio! + " " + dataFine! + " "
let v3 = stato! + " " + tecnico! + " " + descrizione!
return v1 + v2 + v3
}
}
and this is the my code for save the data (it works)
#IBAction func addButton(_ sender: Any){
//genero la data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
let strDate = dateFormatter.string(from: myDatePicker.date)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
let newInt = NSEntityDescription.insertNewObject(forEntityName: "Intervento", into: managerContext)
newInt.setValue(1, forKey: "id")
newInt.setValue(strDate, forKey: "dataInizio")
newInt.setValue("", forKey: "dataFine")
newInt.setValue(2, forKey: "cliente")
newInt.setValue("Matt", forKey: "tecnico")
newInt.setValue(stato[1], forKey: "stato")
newInt.setValue("", forKey: "descrizione")
newInt.setValue("", forKey: "foto")
do{
try managerContext.save()
print("saved")
}catch let error as NSError{
//errore salvataggio
print("Could not fetch \(error), \(error.userInfo)")
}
}
First of all, your class should implement this protocols:
UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate
You can load data from a persistent storage with this code:
//load data
//persistant container
let persistentContainer = NSPersistentContainer.init(name: "Model")
//
lazy var fetchedResultsController: NSFetchedResultsController<Intervento> = {
// Create Fetch Request
let fetchRequest: NSFetchRequest<Intervento> = Intervento.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "dataInizio", ascending: false)]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managerContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
//carica
func load(){
//persistant container
persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
if let error = error {
print("Unable to Load Persistent Store")
print("\(error), \(error.localizedDescription)")
} else {
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
iTableView.delegate = self
iTableView.dataSource = self
// trigger load
load()
}
Your data will be fetched and will show up because you have delegated iTableViewController by this line of code:
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
In order to make this work, your ViewController must conform to NSFetchedResultsControllerDelegate protocol, here you can find a possibile implementation:
extension iTableViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
if let indexPath = newIndexPath {
iTableView.insertRows(at: [indexPath], with: .fade)
}
break;
case .delete:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
break;
case .update:
if let indexPath = indexPath, let cell = iTableView.cellForRow(at: indexPath) {
configureCell(cell, at: indexPath)
}
break;
case .move:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
if let newIndexPath = newIndexPath {
iTableView.insertRows(at: [newIndexPath], with: .fade)
}
break;
}
}
}
In conclusion, this is the full implementation of your iTableViewController (I have included also the necessary code for deleting an row):
class iTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet var iTableView: UITableView!
//load data
//persistant container
let persistentContainer = NSPersistentContainer.init(name: "Model")
//
lazy var fetchedResultsController: NSFetchedResultsController<Intervento> = {
// Create Fetch Request
let fetchRequest: NSFetchRequest<Intervento> = Intervento.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "dataInizio", ascending: false)]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
// Create Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managerContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
//carica
func load(){
//persistant container
persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
if let error = error {
print("Unable to Load Persistent Store")
print("\(error), \(error.localizedDescription)")
} else {
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to Perform Fetch Request")
print("\(fetchError), \(fetchError.localizedDescription)")
}
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
iTableView.delegate = self
iTableView.dataSource = self
// trigger load
load()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//numero di sezioni
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//numero di righe
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let quotes = fetchedResultsController.fetchedObjects else { return 0 }
return quotes.count
}
//gestisco la cella
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//genero la cella
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
configureCell(cell, at:indexPath)
return cell
}
//swipe and delete
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
if(editingStyle == .delete){
// Fetch Quote
let quote = fetchedResultsController.object(at: indexPath)
// Delete Quote
quote.managedObjectContext?.delete(quote)
}
}
// click cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
performSegue(withIdentifier: "segueDetail", sender: self)
}
func configureCell(_ cell: UITableViewCell, at indexPath: IndexPath) {
//get intervento
let intervento = fetchedResultsController.object(at: indexPath)
//fill the cell
cell.textLabel?.text = intervento.dataInizio
cell.detailTextLabel?.text = "SOME_THING"
}
//quando appare aggiorno la table view
override func viewDidAppear(_ animated: Bool){
self.iTableView.reloadData()
}
}
extension iTableViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
iTableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
if let indexPath = newIndexPath {
iTableView.insertRows(at: [indexPath], with: .fade)
}
break;
case .delete:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
break;
case .update:
if let indexPath = indexPath, let cell = iTableView.cellForRow(at: indexPath) {
configureCell(cell, at: indexPath)
}
break;
case .move:
if let indexPath = indexPath {
iTableView.deleteRows(at: [indexPath], with: .fade)
}
if let newIndexPath = newIndexPath {
iTableView.insertRows(at: [newIndexPath], with: .fade)
}
break;
}
}
}
Don't forget to use always the same context when you make operations with your data (for example when you add new data).
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managerContext = appDelegate.persistentContainer.viewContext
For a full example you should look here: https://github.com/bartjacobs/ExploringTheFetchedResultsControllerDelegateProtocol
Swift 3.3
lazy var fetchedResultsController: NSFetchedResultsController<SavedContact> = {
let fetchReqest: NSFetchRequest<SavedContact> = SavedContact.fetchRequest()
fetchReqest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReqest, managedObjectContext: Database.shared.mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override func viewDidLoad() {
super.viewDidLoad()
self.performFetch()
}
func performFetch() {
do {
try self.fetchedResultsController.performFetch()
} catch {
Debug.Log(message: "Error in fetching Contact \(error)")
}
}
//MARK: - NSFetchResultcontrollerDelegate
extension YourViewC: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
}
// Database Class
private let _singletonInstance: Database = Database()
class Database: NSObject {
class var shared: Database {
return _singletonInstance
}
override init() {
super.init()
}
// Main Managed Object Context
lazy var mainManagedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = coordinator
return mainManagedObjectContext
}()
// Persistent Store Coordinator
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("AppName.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: mOptions)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
abort()
}
return coordinator
}()
// DB Directory and Path
lazy var applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectoryURL = urls[urls.count - 1] as URL
let dbDirectoryURL = documentDirectoryURL.appendingPathComponent("DB")
if FileManager.default.fileExists(atPath: dbDirectoryURL.path) == false{
do{
try FileManager.default.createDirectory(at: dbDirectoryURL, withIntermediateDirectories: false, attributes: nil)
}catch{
}
}
return dbDirectoryURL
}()
}

How do I pull the first result from some json?

I'm using Swift, and I haven't had this problem before. This is my code:
var item = jsonResult["items"][0] as! NSDictionary
I get an error for the whole line saying
Cannot subscript a value of type 'AnyObject?' with an index of type 'Int'
Any help?
The rest of my code is here:
import UIKit
import CoreData
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var detailViewController: DetailViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil
override func awakeFromNib() {
super.awakeFromNib()
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
self.clearsSelectionOnViewWillAppear = false
self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let urlPath = "https://www.googleapis.com/blogger/v3/blogs/10861780/posts?key=AIzaSyDZ86TotGGMQpRJ6GCNrCMDMaqwbQAbfz4"
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error:nil) as!
NSDictionary
var item = jsonResult["items"][0] as! NSDictionary
}
})
task.resume()
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
newManagedObject.setValue(NSDate(), forKey: "timeStamp")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
// 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.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = "Blog Item"
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
var error: NSError? = nil
if !context.save(&error) {
// 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.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
cell.textLabel!.text = object.valueForKey("timeStamp")!.description
}
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Event", 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: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
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
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// 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.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.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.
self.tableView.reloadData()
}
*/
}
I can't be sure what's going on because you haven't provided much to go on.
You didn't provide the code that deserialized your JSON data into objects. It's probably a collection of Foundation objects.
It sounds like jsonResult is an NSDictionary. If so, the value for each key is going to be of type AnyObject? (optional AnyObject.)
You will need to cast it to the array type you're expecting:
if let array = jsonResult["items"] as? NSArray,
let item = array[0] as? NSDictionary
{
//Unwrapped the dictionary inside the array inside the dictionary.
}
else
{
//There's something rotten in the state of Denmark
}
var item = ( jsonResult["items"] as! [ NSDictionary ] )[ 0 ]
Or restrict keys to String type:
var item = ( jsonResult["items"] as! [ [ String: AnyObject ] ] )[ 0 ]
This will various according to the json that you get. It will be better if you post the json result here.
jsonResult will definitely be a NSDictionary. But tbe type of jsonResult["item"] is still unclear to me.
If the jsonResult["item"] is a NSDictionary, I guess there is not such a saying as "the first element". NSDictionary is a key-value pair, I'm not sure whether it has some sort of sequence for it.
If jsonResult["item"] is an array, you should get it and set it to a NSArray. Use let itemArray = jsonResult["item"] as! NSArray instead. Then you can use itemArray[0] to get the first item.
So please check what jsonResult["item"] exactly is.
Hope this helps.

Swift UISearchController wired up in Core Data Project, app runs, but search not updating

I have been wrestling the past few days with creating a filter in a Swift project for a TableViewController that uses Core Data. I finally figured out I need to use a UISearchController, create an NSPredicate for the searchController.searchBar, etc.
I found this post EXTREMELY helpful, but after modeling my TVC after this project, I find "all the lights are on, but nobody's home". I can search, predicate is created in searchBar, add, remove, etc. but the cells don't update for the search. I'm missing something, but I don't know what.
Here are the relevant portions of my code.
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchResultsUpdating
// Properties that get instantiated later
var detailViewController: DetailViewController? = nil
var addNoteViewController:AddNoteViewController? = nil // I added this
var managedObjectContext: NSManagedObjectContext? = nil
// Added variable for UISearchController
var searchController: UISearchController!
var searchPredicate: NSPredicate? // I added this. It's optional on and gets set later
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
if let split = self.splitViewController {
let controllers = split.viewControllers
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
// UISearchController setup
searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = searchController?.searchBar
self.tableView.delegate = self
self.definesPresentationContext = true
}
// MARK: - UISearchResultsUpdating Delegate Method
// Called when the search bar's text or scope has changed or when the search bar becomes first responder.
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = self.searchController?.searchBar.text // steve put breakpoint
println(searchController.searchBar.text)
if let searchText = searchText {
searchPredicate = NSPredicate(format: "noteBody contains[c] %#", searchText)
self.tableView.reloadData()
println(searchPredicate)
}
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
var note: Note
if searchPredicate == nil {
note = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Note
} else {
let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
}
note = filteredObjects![indexPath.row] as! Note
}
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(note)
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Note", 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: "noteTitle", ascending: false)
let sortDescriptors = [sortDescriptor]
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
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// 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.
println("Unresolved error \(error), \(error?.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func controllerWillChangeContent(controller: NSFetchedResultsController) {
// self.tableView.beginUpdates() // ** original code, change if doesn't work** steve put breakpoint here
// ANSWER said this section is redundant, but keeping it b/c it doesn't crash
if searchPredicate == nil {
tableView.beginUpdates()
} else {
(searchController.searchResultsUpdater as! MasterViewController).tableView.beginUpdates()
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as! MasterViewController).tableView
}
switch type {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
I think my problem "lives" here in this section. Autocomplete has been my friend up to here, but I'm don't see "searchIndex" referenced in AutoComplete. I think I'm missing something, but I'm not sure what or how.
If you've made it this far, thanks for reading. Here's the GitHub repo for the branch I'm working on.
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
var tableView = UITableView()
if self.searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (self.searchController.searchResultsUpdater as! MasterViewController).tableView
}
switch type {
case .Insert:
println("*** NSFetchedResultsChangeInsert (object)")
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
println("*** NSFetchedResultsChangeDelete (object)")
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
println("*** NSFetchedResultsChangeUpdate (object)")
// TODO: need fix this
// ORIGINAL CODE
// self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code
// PROSPECTIVE SOLUTION CODE
println("*** NSFetchedResultsChangeUpdate (object)")
if searchPredicate == nil {
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code
} else {
// Should search the do something w/ the UISearchControllerDelegate or UISearchResultsUpdating
// Instead of "indexPath", it should be "searchIndexPath"--How?
let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell // My cell is a vanilla cell, not a xib
let location = controller.objectAtIndexPath(searchIndexPath) as Location // My object is a "Note"
cell.configureForLocation(location) // This is from the other guy's code, don't think it's applicable to me
}
case .Move:
println("*** NSFetchedResultsChangeMove (object)")
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
if self.searchPredicate == nil {
self.tableView.endUpdates()
} else {
println("controllerDidChangeContent")
(self.searchController.searchResultsUpdater as! MasterViewController).tableView.endUpdates()
}
}
Edit: Per #pbasdf, I'm adding the TableView methods.
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchPredicate == nil {
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
The problems lie in your tableView datasource methods: numberOfSectionsInTableview:, tableView:numberOfRowsInSection:, and tableView:cellForRowAtIndexPath:. You need each of those methods to return different results if the searchPredicate is not nil - much like your tableView:commitEditingStyle: method does. I would make filteredObjects an instance property (defined at the start of the class) so that all those methods can access it:
var filteredObjects : [Note]? = nil
Now, when the search text changes, you want to rebuild the filteredObjects array. So in updateSearchResultsForSearchController, add a line to recompute it based on the new predicate:
if let searchText = searchText {
searchPredicate = NSPredicate(format: "noteBody contains[c] %#", searchText)
filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
} as [Note]?
self.tableView.reloadData()
println(searchPredicate)
}
I would also recommend (for simplicity) that when you activate the search, the results are displayed all in one section (otherwise you have to work out how many sections your filtered results fall into - possible but tiresome):
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if searchPredicate == nil {
return self.fetchedResultsController.sections?.count ?? 0
} else {
return 1
}
}
Next, if the searchPredicate is not nil, the number of rows in the section will be the count of filteredObjects:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.searchPredicate == nil {
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
} else {
return filteredObjects?.count ?? 0
}
}
Finally, if the searchPredicate is not nil, you need to build the cell using the filteredObjects data, rather than the fetchedResultsController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
self.configureCell(cell, atIndexPath: indexPath)
return cell
} else {
// configure the cell based on filteredObjects data
...
return cell
}
}
Not sure what labels etc you have in your cells, so I leave it to you to sort that bit.