Core data causing miss layout - swift

I have a generic viewModel that handles core data delegations. But when I insert a new data to the context core data gives me a weird error:
[error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 1 from section 1 which only contains 1 rows before the update with userInfo (null)
CoreData: fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 1 from section 1 which only contains 1 rows before the update with userInfo (null)
I don't delete any of the rows. Actually I'm just inserting. Here is my viewModel's delegation
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
delegate?.endUpdates()
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
delegate?.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 {
delegate?.insertItemAt(indexPath: newIndexPath)
}
if let indexPath = indexPath {
delegate?.insertItemAt(indexPath: indexPath)
}
case .delete:
if let indexPath = indexPath {
delegate?.deleteItemAt(indexPath: indexPath)
}
case .move:
if let newIndexPath = newIndexPath {
delegate?.insertItemAt(indexPath: newIndexPath)
}
if let indexPath = indexPath {
delegate?.deleteItemAt(indexPath: indexPath)
}
case .update:
if let indexPath = indexPath {
delegate?.updateItemAt(indexPath: indexPath)
}
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
let indexSet: IndexSet = IndexSet(integer: sectionIndex)
switch type {
case .insert:
delegate?.insertSection(indexSet: indexSet)
case .delete:
delegate?.deleteSection(indexSet: indexSet)
default:
break
}
}
Here is the ViewController that responds to actions
extension ChatVC: CoreDataViewModelDelegate {
func insertSection(indexSet: IndexSet) {
tableView.insertSections(indexSet, with: .automatic)
}
func deleteSection(indexSet: IndexSet) {
tableView.deleteSections(indexSet, with: .automatic)
}
func beginUpdates() {
tableView.beginUpdates()
}
func endUpdates() {
tableView.endUpdates()
}
func insertItemAt(indexPath: IndexPath) {
tableView.insertRows(at: [indexPath], with: .fade)
tableView.scrollToBottom(animated: true)
viewModel.sendReadACKForMessageAt(indexPath: indexPath)
}
func deleteItemAt(indexPath: IndexPath) {
tableView.deleteRows(at: [indexPath], with: .fade)
}
func updateItemAt(indexPath: IndexPath) {
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
As far as I know, the core data should handle most of the table view actions. I mean when I pop the screen and push the same VC to the navigation everything works fine. I couldn't solve the problem.

Related

How to properly refactor repeated code of 2 ViewControllers?

I have 1 UIViewController with a type of CustomTableView and 1 UITableViewController with a type of usual UITableView. Both conform to NSFetchedResultsControllerDelegate and implemented its delegate methods with the repeated code. For now it's in extensions.
Is it possible to move that code out to a separate swift file? I tried to move it to separate file with class NSFetchedResultsView, but when I copy that delegate methods to the new file, it doesn't know anything about tableView inside it's methods...
How can I separate that methods properly?
Delegate methods I want to separate:
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 .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
}
default:
tableView.reloadData()
}
}
You can make an object that you can assign to be the delegate
class CommonFetchResultDelegate: NSFetchedResultsControllerDelegate {
var tableView: UITableView
// make init that takes the table view
init(tableView: TableView) {
self.tableView = tableView
}
// !!!implement all the other delegate functions!!!
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 .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
}
default:
tableView.reloadData()
}
}
}
// then in the view controller
var myDelegate: CommonFetchResultDelegate?
override viewDidLoad() {
super.viewDidLoad()
self.myDelegate = CommonFetchResultDelegate(tableView: self.tableView)
self.myDelegate = fetchResultController.delegate
}

UITableView Delete Row Animation misbehaving

I have a table view hooked up to a fetchedResultsController. When the table goes into editing mode I can get the delete to happen but the animation is strange. The red delete button instead of fading up with the rest of the row stays at full height and moves right. Also when swiping to delete the row, red bar stays at full row height for its animation.
The delete operation is surrounded by a beginTableUpdates : endTableUpdates block.
A video of this is at https://youtu.be/YOxex0sROwU
I'm using MVVM (or attempting to!)
I have some closures defined
var willChangeContent: (() -> ())?
var didChangeContent: (() -> ())?
var didInsertRow: ((_ indexPath: IndexPath) -> ())?
var didDeleteRow: ((_ indexPath: IndexPath) -> ())?
var didUpdateRow: ((_ IndexPath: IndexPath) -> ())?
var didMoveRow: (( _ IndexPath: IndexPath, _ newIndexPath: IndexPath) -> ())?
And the fetchedResultsController in an extension to the viewModel
extension TrainingEventsTableViewViewModel: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
willChangeContent?()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
didChangeContent?()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
didInsertRow?(newIndexPath!)
case .delete:
didDeleteRow?(indexPath!)
case .update:
didUpdateRow?(indexPath!)
case .move:
didMoveRow?(indexPath!, newIndexPath!)
}
}
}
and in the uiTableViewController the closures are:
private func bindToModel() {
viewModel?.willChangeContent = { [unowned self] () in
self.tableView.beginUpdates()
}
viewModel?.didChangeContent = { [unowned self] () in
self.tableView.endUpdates()
}
viewModel?.didInsertRow = { [unowned self] (indexPath) in
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
viewModel?.didDeleteRow = { [unowned self] (indexPath) in
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
viewModel?.didUpdateRow = { [unowned self] (indexPath) in
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
viewModel?.didMoveRow = { [unowned self] (indexPath, newIndexPath) in
self.tableView.deleteRows(at: [indexPath], with: .automatic)
self.tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
The only other different thing is that the cell is coming from a xib file rather than a prototype cell on the storyboard.

Swift - after saving in core data tableview is not updating

I'm saving data trough this function
var parameter: Parameter
if parameterToEdit == nil {
parameter = Parameter(context:context)
} else {
parameter = parameterToEdit!
}
if let pH = phTextField.text {
parameter.paramPH = (pH as NSString).doubleValue
}
...........................................................
ad.saveContext()
self.presentingViewController?.dismiss(animated: true, completion: nil)
my problem is that after saving the table view is not updating.
in my tableviewcontroller i have implemented this functions:
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! ParamCell
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
}
}
Not sure why is not updating.... any idea?

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?

Delete row from UITableView using NSFetchedResultsController

I am trying to delete a row in UITableView. The data for the table comes from core data entity using NSFetchedResultsController. This is my code to delete the row:
func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
let managedObject:NSManagedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
context?.deleteObject(managedObject)
do {
try context?.save()
} catch {
print("error")
}
favoritesList.reloadData() // UITableView
}
}
Should be simple but I can't seem to delete a row. Please help...
Here is the part of my code deleting rows:
override func tableView(_: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
//creating a delete button
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete") { (UITableViewRowAction, NSIndexPath) -> Void in
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
let restaurantToRemove = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Pub
managedObjectContext.deleteObject(restaurantToRemove)
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch let nserror as NSError {
NSLog("some error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
deleteAction.backgroundColor = UIColor.redColor()
return [deleteAction]
}
and these three methods:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
default:
tableView.reloadData()
}
pubs = controller.fetchedObjects as! [Pub]
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}