UITableView didSelectRowAt returns wrong tableViewCell at indexPath - swift

I a making a todo plan app, and I have encountered a serious bug in my tableViewCell. Whenever I select a row more than once, the wrong indexPath at row is called.
If I comment out my resultsController.delegate the app works normally. However I need the results controller delegate for to update the table view every time a new plan is made.
override func viewDidLoad() {
super.viewDidLoad()
// Create Request
let request:NSFetchRequest<Plan> = Plan.fetchRequest()
let sortDescriptors = NSSortDescriptor(key: "date", ascending: true) // make request more specific
request.sortDescriptors = [sortDescriptors]
// Init our results controller
resultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: coreDataStack.managedContext,
sectionNameKeyPath: nil,
cacheName: nil
)
resultsController.delegate = self
// Perform fetch request
do {
try resultsController.performFetch()
} catch {
print("Perform fetch error \(error)")
}
}
Here are the relevant TableViewDelegates
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showNewPlan", sender: tableView.cellForRow(at: indexPath))
}
Also i have also added my prepare function for navigation.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let _ = sender as? UIBarButtonItem , let vc = segue.destination as? NewPlanViewController {
vc.managedContext = resultsController.managedObjectContext
}
if let cell = sender as? UITableViewCell , let vc = segue.destination as? NewPlanViewController {
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell) {
let plan = resultsController.object(at: indexPath)
vc.plan = plan
}
}
}
The resultsControllerDelegate is how I update my tableViewCells.
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: .automatic)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) {
let plan = resultsController.object(at: indexPath)
cell.textLabel?.text = plan.title
}
default:
break
}
}

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
}

Getting user input to copy from a TableViewController to a ViewController using Swift

I'm working on an app in which the user inputs three values (artist, album and release date). I'm trying to get those user entered values to 'copy' over to another TableViewController.
The values show on this, the FreshReleaseTableViewController (the code below) and I need them to copy to EditFreshReleaseViewController.
How would I go about this?
import UIKit
import CoreData
import UserNotifications
class FreshReleaseTableViewController: UITableViewController{
var freshreleases = [Release_Date]()
let dateFormatter = DateFormatter()
override func viewDidLoad() {
super.viewDidLoad()
//create a new button
let button = UIButton.init(type: .custom)
//set image for button
button.setImage(UIImage(named: "Mic App Logo.png"), for: UIControlState.normal)
dateFormatter.dateStyle = .full
dateFormatter.timeStyle = .none
}
#objc func editAction() {
let viewController = AddfreshreleaseViewController()
navigationController?.present(viewController, animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = Release_Date.fetchRequest() as NSFetchRequest<Release_Date>
let sortDescriptor1 = NSSortDescriptor(key: "artist", ascending: true)
let sortDescriptor2 = NSSortDescriptor(key: "album", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2]
do {
freshreleases = try context.fetch(fetchRequest)
} catch let error {
print("Could not fetch because of error: \(error).")
}
/*let startOfToday = Calendar.current.startOfDay(for: Date()) as NSDate
let predicate = NSPredicate(format: "release_date > %#", startOfToday)
fetchRequest.predicate = predicate*/
NSFetchedResultsController<NSFetchRequestResult>()
fetchRequest.predicate = NSPredicate(format: "isReleased = NO")
tableView.reloadData()
}
// MARK: - Table view data source
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 freshreleases.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FreshReleaseCellIdentifier", for: indexPath)
let freshrelease = freshreleases[indexPath.row]
cell.textLabel?.numberOfLines = 0
let artist = freshrelease.artist ?? ""
let album = freshrelease.album ?? ""
cell.textLabel?.text = artist + "'s\nnew album '" + album + "'\nreleases"
if let date = freshrelease.release_date as Date? {
cell.detailTextLabel?.text = dateFormatter.string(from: date)
} else {
cell.detailTextLabel?.text = ""
}
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if freshreleases.count > indexPath.row {
let freshrelease = freshreleases[indexPath.row]
// Remove notification
if let identifier = freshrelease.release_dateId {
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: [identifier])
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
context.delete(freshrelease)
freshreleases.remove(at: indexPath.row)
do {
try context.save()
} catch let error {
print("Could not save \(error)")
}
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
#available(iOS 11.0, *)
override func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let modifyAction = UIContextualAction(style: .normal, title: "Edit", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("Update action ...")
let MainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc : UIViewController = MainStoryboard.instantiateViewController(withIdentifier: "FreshReleaseEdit") as UIViewController
self.present(vc, animated: true, completion: nil)
success(true)
})
modifyAction.title = "Edit"
modifyAction.backgroundColor = .blue
return UISwipeActionsConfiguration(actions: [modifyAction])
}
/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
}
*/
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
}
Add to your EditFreshReleaseViewController variable which represents selected fresh release
var selectedFreshRelease: Release_Date?
now in line where you're declaring view controller which is gonna be presented, downcast this controller as EditFreshReleaseViewController
let vc = MainStoryboard.instantiateViewController(withIdentifier: "FreshReleaseEdit") as! EditFreshReleaseViewController
then just assign its variable as item from freshReleases array on index indexPath.row
vc.selectedFreshRelease = self.freshReleases[indexPath.row]
finally, present this view controller as you do
self.present(vc, animated: true, completion: nil)

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()
}

Swift CoreData saving and fetching from a relationship

I have coredata in my swift app. I am able to get the two textfields.text to save to core data and the fetch it, but those 2 properties are part of 1 entity. I also have a few other entities with relationships all set up.
When I save to core data what happens is the 2 strings come through, but the relationship strings are nil and the app crashes.
Here is some of my set up
// MARK: NSFetchedResultsControllerDelegate
extension SavedPalauttesViewController: 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{
let cell = tableView.cellForRow(at: indexPath) as! LocalPalautteTableViewCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
}
break
case .move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
func attemptFetch() {
let fetchRequest: NSFetchRequest<Palautte> = Palautte.fetchRequest()
let nameSort = NSSortDescriptor(key: "name", ascending: false)
fetchRequest.sortDescriptors = [nameSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
}
Now the category of type string and name of type string work fine
but when I try to save any of the background colors or foreground...I get nil and a crash
this is part of how I am attempting to save to core data
palauttePalautte.category = finalPalautteCategoryValue ?? ""
palauttePalautte.toBackgroundColor?.redValue = String(Int(redBackgroundColorValue))
but on the 2nd vc when I try to get a value for it I crash
let backRed = palautte.toBackgroundColor?.redValue ?? ""
What am I doing wrong?

VERY NEW TO SWIFT fatal error unexpectedly found nil while unwrapping an optional vaule [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
After more research and reviewing the articles pointed out: I concluded my code was actually correct. I removed the .plist file of my app from the simulator documents folder allowing a new one to be created from my new viewcontrollers.
I am receiving a fatal error. My app does build without error to the simulator; However nothing is displayed and the app crashes. The debugger window states "fata error: unexpectedly found nil while unwrapping an Optional value". I believe the error is because the forKey referring to the lists which references an empty array/
import UIKit
class AllListsViewController: UITableViewController, ListDetailViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lists.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = makeCell(for: tableView)
let checklist = lists[indexPath.row]
cell.textLabel!.text = checklist.name
cell.accessoryType = .detailDisclosureButton
return cell
}
func makeCell(for tableView: UITableView) -> UITableViewCell {
let cellIdentifier = "Cell"
if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
return cell
} else {
return UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let checklist = lists[indexPath.row]
performSegue(withIdentifier: "ShowChecklist", sender: checklist)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowChecklist" {
let controller = segue.destination as! ChecklistViewController
controller.checklist = sender as! Checklist
} else if segue.identifier == "AddChecklist" {
let navigationController = segue.destination
as! UINavigationController
let controller = navigationController.topViewController
as! ListDetailViewController
controller.delegate = self
controller.checklistToEdit = nil
}
}
var lists: [Checklist]
required init?(coder aDecoder: NSCoder) {
lists = [Checklist]()
super.init(coder: aDecoder)
loadChecklists()
}
override func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCellEditingStyle,
forRowAt indexPath: IndexPath) {
lists.remove(at: indexPath.row)
let indexPaths = [indexPath]
tableView.deleteRows(at: indexPaths, with: .automatic)
}
func listDetailViewControllerDidCancel(
_ controller: ListDetailViewController) {
dismiss(animated: true, completion: nil)
}
func listDetailViewController(_ controller: ListDetailViewController,
didFinishAdding checklist: Checklist) {
let newRowIndex = lists.count
lists.append(checklist)
let indexPath = IndexPath(row: newRowIndex, section: 0)
let indexPaths = [indexPath]
tableView.insertRows(at: indexPaths, with: .automatic)
dismiss(animated: true, completion: nil)
}
func listDetailViewController(_ controller: ListDetailViewController,
didFinishEditing checklist: Checklist) {
if let index = lists.index(of: checklist) {
let indexPath = IndexPath(row: index, section: 0)
if let cell = tableView.cellForRow(at: indexPath) {
cell.textLabel!.text = checklist.name
}
}
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView,
accessoryButtonTappedForRowWith indexPath: IndexPath) {
let navigationController = storyboard!.instantiateViewController(
withIdentifier: "ListDetailNavigationController")
as! UINavigationController
let controller = navigationController.topViewController
as! ListDetailViewController
controller.delegate = self
let checklist = lists[indexPath.row]
controller.checklistToEdit = checklist
present(navigationController, animated: true, completion: nil)
}
func documentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func dataFilePath() -> URL {
return documentsDirectory().appendingPathComponent("Checklists.plist")
}
func saveChecklists() {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(lists, forKey: "Checklists")
archiver.finishEncoding()
data.write(to: dataFilePath(), atomically: true)
}
func loadChecklists() {
let path = dataFilePath()
if let data = try? Data(contentsOf: path) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
*lists = unarchiver.decodeObject(forKey: "Checklists") as! [Checklist]* ****The Error occurs on this line****
unarchiver.finishDecoding()
}
}
}
as! is explicitly unwrapping the optional. as? will prevent an app crash by simply returning nil if downcasting fails.
if let checklist = (NSKeyedUnarchiver(forReadingWith: data)?.decodeObject(forKey: "Checklists")) as? [Checklist] {
lists = checklist
}
else {
// set lists as appropriate
}