Update the tableView - without refresh completely - swift

I have created the table "tableView" (data from CoreData - NSManagedObject). I want to update the data in the tables when the new notification comes from NSNotification without refreshing the table completely. How to do it ?
Thanks for help!

If you want to back tableview with autoupdating results from CoreData then its best to use NSFetchedResultsController. Apple has sample code on how to do this here.
The sort version is you implement the delegate methods for willChangeContent and didChangeContent and forward the relevant changes (ie insert, delete and reload row) to the tableView, and this will keep your tableview in synch with your database.
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)
case .move:
break
case .update:
break
}
}
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: [indexPath!], with: .fade)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}

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?

TableView with NSFetchedResultsController is Flickering so much

I got a tableView with FRC. I use NSMergeByPropertyObjectTrumpMergePolicy as mergePolicy for managedContext save.
The problem is when a record updated "didChange" delegate method is calling 3 times. It's calling for in that order insert, delete, update. So my tableView flickering so much.
How can i prevent that? Because every record of my application is being updated 4 times so tableView is flickering 12 times..
My delegate methods:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
case .delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .none)
case .move:
break
case .update:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
print("cs \(type.rawValue)")
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath! as IndexPath], with: .none)
break
case .delete:
tableView.deleteRows(at: [indexPath!], with: .none)
break
case .move:
tableView.deleteRows(at: [indexPath!], with: .none)
tableView.insertRows(at: [newIndexPath!], with: .none)
//tableView.moveRow(at: indexPath!, to: newIndexPath!)
break
case .update:
tableView.reloadRows(at: [indexPath!], with: .none)
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
Make sure that you are not cleaning your database. Flickering is possible because of this:
service.fetchDatasWith { (result) in
switch result {
case .success(let data):
DataManager.clearData() // Cleaning
DataManager.saveInCoreDataWith(array: data) // Saving again
print(data)
case .error(let message):
DispatchQueue.main.async {
AlertPresenter.presentAlertWith(title: "Error", message: message, controller: self)
}
}
}

Fetch One Attribute of NSManagedObject after Pressing a Button in UITableViewCell

I'm using core data and I have a tableView to display all user's approvals
I'm using NSFetchedResultsController to fetch the approvals from core data
I have added these methods which are used when the delegate is notified that the content will change, we tell the tableView to begin updates
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
// 1
switch type {
case .Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
case .Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
default: break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
// 2
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
default: break
}
}
what I understood: the NSFetchtedResultsController can monitor changes to the NSManagedObject context and update the table to reflect those changes
In tableViewCell I have "inquire Again" button which will update the Approval Status
and this is ApprovalObject Class:
public class ApprovalObject: NSManagedObject {
#NSManaged public var reference_no: NSNumber
#NSManaged public var status: String
#NSManaged public var date: NSDate
#NSManaged public var card: Card
#NSManaged public var hospital: Hospital
}
my question is: how to get the value of "status" attribute for specific "ApprovalObject" after pressing the button in the tableViewCell ?? can I just call tableView.beginUpdates() ??
#IBAction func inquireApprovalBtn(sender: AnyObject) {
// what should i do here ???
}
It is enough to modify your data object, e.g. the status of your ApprovalObject instance (and save if desired). The NSFetchedResultsControllerDelegate should pick up on that and update the cell in question.
Of course, you have at some point deleted the .Change case in your switch statement in the delegate callback. Make sure you put it back in. (You can copy it from a "Master-Detail" Xcode template.)