I spent some time in creating a fix for We All Pay. The issue was a invalid data on screen when editing a payment on an iPad running iOS 8. The entire issue was caused by a bug in iOS 8 combined with Swift 2.2 and I wasn’t aware of it.
If you fetch data using NSFetchedResultsController on iOS 8 and you present that data on the screen everything is fine. However when you edit that data and write it to the Core Data database the NSFetchedResultsController will thing your changing values but also that you are inserting records. Resulting in a warning and not updating the screen properly. In the log file you can find the following error:
1 |
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3347.44.2/UITableView.m:1406 |
So I was using this code to update the screen and its Objective-C counter part works just fine. Also for iOS 9 it works fine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// MARK: NS Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { debugPrint("controllerWillChangeContent") tableView.beginUpdates() } func controllerDidChangeContent(controller: NSFetchedResultsController) { debugPrint("controllerDidChangeContent") tableView.endUpdates() } 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: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic) case .Move: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) } / } |
On iOS 8 it causes trouble. In order to solve these problems I suggest the following workaround:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// MARK: NS Fetched Results Controller Delegate func controllerWillChangeContent(controller: NSFetchedResultsController) { debugPrint("controllerWillChangeContent") tableView.beginUpdates() } func controllerDidChangeContent(controller: NSFetchedResultsController) { debugPrint("controllerDidChangeContent") tableView.endUpdates() } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { if #available(iOS 9, *) { switch (type) { case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic) case .Move: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) } } else { switch (type) { case .Insert: if indexPath == nil { tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) } case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic) case .Move: if indexPath == newIndexPath { tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic) } else { tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) } } } } |
Cheers, if you have question or remarks please let me know.