UITableView Delete Row Animation misbehaving - swift

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.

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
}

Core data causing miss layout

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.

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?

NSFetchedResultsController not Updating UI (Swift)

I've been stuck on this for a few days, and can't seem to figure it out.
I'm using NSFetchedResultsController with CoreDate, and would like to update the UI of a row as a Core Data Object is updated. In my case when a user completes an exercise, the value of the status changes to 1. Funny thing, I can print out the correct values, but can't update the UI. Thanks!
class CoreDataTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController? {
didSet {
do {
if let frc = fetchedResultsController {
frc.delegate = self
try frc.performFetch()
}
tableView.reloadData()
} catch let error {
print("NSFetchResultsController.performFetch() failed: \(error)")
}
}
}
// Mark: UITableViewDataSource (Part 1)
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController?.sections where sections.count > 0 {
return sections[section].numberOfObjects
} else {
return 0
}
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController?.sections where sections.count > 0 {
return sections[section].name
} else {
return nil
}
}
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return fetchedResultsController?.sectionIndexTitles
}
override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
return fetchedResultsController?.sectionForSectionIndexTitle(title, atIndex: index) ?? 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ExerciseCell", forIndexPath: indexPath)
configureCell(cell, atIndexPath: indexPath)
return cell
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let exerciseObject = fetchedResultsController!.objectAtIndexPath(indexPath) as! CoreExerciseForPlan
let cell = tableView.dequeueReusableCellWithIdentifier("ExerciseCell", forIndexPath: indexPath)
cell.detailTextLabel?.text = exerciseObject.exercise?.name
cell.textLabel?.text = String(exerciseObject.status)
}
// Mark: NSFetchedResultsControllerDelegate (Part 2)
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert: tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Update: tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete: tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default: break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
let exerciseObject = fetchedResultsController!.objectAtIndexPath(indexPath!) as! CoreExerciseForPlan
switch type {
case .Update:
print("indexPath: \(indexPath!.row)")
print("object: \(exerciseObject.exercise!.name) with status \(exerciseObject.status)")
**// These print the correct values for status!!!!**
configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Move:
tableView.deleteRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
}

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