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.)
Related
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()
}
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.
I am new to iOS development and am in my second app with Swift. I am unable to incorporate SearchBar & Display search in my Table view controller.
There are 2 views in my app. First View generates an Array of fruits. Output of First View: [Apple, Orange, Banana, Pears]. Then [Pomegranate, Pears, Watermelon, Muskmelon] and so on. One array at a time.
Each array of fruits is stored in core-data using NSFetchedResultsController to be displayed in a tableviewcontroller.
My Coredata layout:
import UIKit
import CoreData
#objc(Fruits)
class Fruits: NSManagedObject {
#NSManaged var date: String
#NSManaged var fruit: String
}
My Second View displays the list of fruits. Now I have added a Search Bar & Display on top on the table view. But I am unable to make it work.
Protocols used:
class HistoryTableViewController: UITableViewController, NSFetchedResultsControllerDelegate{
Variables declared:
let managedObjectContext: NSManagedObjectContext? = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var fetchedResultsController: NSFetchedResultsController?
var fetchRequest = NSFetchRequest(entityName: "Fruits")
There are two outlets in my class:
#IBOutlet weak var searchBar: UISearchBar! // the Search Bar & Display
#IBOutlet var tblHistory: UITableView! = nil // Table View
Data population for table view:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: (NSIndexPath!)) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
if let cellFruit = fetchedResultsController?.objectAtIndexPath(indexPath) as? Fruits
{
cell.textLabel?.text = cellFruit.fruit
cell.detailTextLabel?.text = cellFruit.date
}
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
//MARK: NSFetchedResultsController Delegate Functions
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Move:
break
case NSFetchedResultsChangeType.Update:
break
default:
break
}
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
//Delete object from entity, remove from list
if editingStyle == .Delete {
}
switch editingStyle {
case .Delete:
managedObjectContext?.deleteObject(fetchedResultsController?.objectAtIndexPath(indexPath) as! Fruits)
do {
try managedObjectContext?.save()
}catch{}
print("Fruit set deleted successfully.")
case .Insert:
break
case .None:
break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertRowsAtIndexPaths(NSArray(object: newIndexPath!) as! [NSIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Delete:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as! [NSIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Move:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as! [NSIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertRowsAtIndexPaths(NSArray(object: newIndexPath!) as! [NSIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Update:
tableView.cellForRowAtIndexPath(indexPath!)
break
default:
break
}
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
How do I Search any fruit from coredata and filter on the view?
Which delegate function should be used for the search bar & display functionality in Swift coding?
Any help on a concept I might have missed somewhere in here would be appreciated! Thanks.
Update: as Daniel Eggert suggested I am trying to use predicate for the Search Controller functionality, but the Predicate always gives a nil. What am I missing???
Variables declared in class:
var results: NSArray = []
var searchPredicate: NSPredicate?
var searchController: UISearchController!
Predicate Functions:
//Search Functionality
func updateSearchResultsForSearchController(searchController: UISearchController)
{
let searchText = self.searchController?.searchBar.text
if let searchText = searchText {
searchPredicate = NSPredicate(format: "fruit contains[cd] %#", searchText)
results = (self.fetchedResultsController!.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
} as! [Fruits]?)!
self.tableView.reloadData()
}
}
// Called when text changes (including clear)
func searchBar(searchBar: UISearchBar, textDidChange searchText: String)
{
if !searchText.isEmpty
{
var predicate: NSPredicate = NSPredicate()
predicate = NSPredicate(format: "fruit contains [cd] %#", searchText)
let sortDescriptor = NSSortDescriptor(key: "date", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultsController?.fetchRequest.predicate? = predicate
print(fetchedResultsController?.fetchRequest.predicate) // this is always nil. Why????????????
do {
try fetchedResultsController?.performFetch()
}catch{}
print("results array: \(results)") // this array should have values of the table view, but is empty. Why ?????????????????
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext!,
sectionNameKeyPath: "fruit", cacheName: nil)
fetchedResultsController?.delegate = self
tableView.reloadData()
}
}
You want to set a predicate on you fetched results controller.
Once you've changed the fetch request on the fetched results controller you need to call performFetch() to have it refetch its data. Then call reloadData() on the table view.
Text search for non-trivial examples can be tricky to do right, though, due to how languages, locales, and Unicode interact. I'd recommend the Text chapter of my book: https://www.objc.io/books/core-data/
If you want to implement searchable tableView? You're must must use UISearchResultsUpdating protocol method instead of the UISearchBarDelegate protocol method called searchBar:textDidChange. You can use searchBar:textDidChange method when UISearchResultsUpdating protocol method is not available! Both classes are useful and powerful. And easy to implement
I have a problem with update contact data. View no.1 main view with tableView i have list of contacts. After tap some person from list i have next tableView (view no.2 [push from view 1 to view 2]) with details and button with EDIT. Next if i press Edit i have modal view no3 with edit input when i can change NAME. After SAVE how can i refresh
title in tableView navBar
title in backButton
main tableView in view1
i am using in my view no1
let managedObjectContext: NSManagedObjectContext? = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var fetchedResultsController: NSFetchedResultsController?
override func viewDidLoad() {
super.viewDidLoad()
self.configureView()
fetchedResultsController = NSFetchedResultsController(fetchRequest: CLInstance.allContactsFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController?.delegate = self
do { try fetchedResultsController?.performFetch() } catch _ {}
tableView.reloadData()
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(
controller: NSFetchedResultsController,
didChangeObject anObject: AnyObject,
atIndexPath indexPath: NSIndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths(NSArray(object: newIndexPath!) as! [NSIndexPath], withRowAnimation: .Fade)
break
case .Delete:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as! [NSIndexPath], withRowAnimation: .Fade)
break
case .Move:
print("move")
//tableView.moveRowAtIndexPath(indexPath!, toIndexPath: newIndexPath!)
break
case .Update:
print("update")
tableView.cellForRowAtIndexPath(indexPath!)
break
}
}
I expected when i save data in this code NSFetchedResultsController make update but on my log file i see print out "MOVE" not "UPDATE"how it is possible to do this in some other way ?
First of all you need to configure Outlets to any of the on screen elements you want to update. Then you just set their .text attribute to the new value.
The tableView is a bit different though, you have to reload it : myTableView.reloadData()
Just a few gotcha's:
If your 'myTableView isn't reloading, make sure that the
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
function where the tableViewCell is created is using the updated source for creating the cell.
Do all your updates on the main thread (if you're using threads)
I have a UIView which has a UITableView along with different components. Its corresponding UIViewController has the viewDidLoad() as follows:
Class Foo: UIViewController, NSFetchedResultsControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
/* some other stuff */
// An instance of a class implementing UITableViewData Source
// and NSFetchedResultsController.
// It also contains the instance of the
// NSFetchedResultsController.
self.namesTable.dataSource = self.namesTableController
self.namesTableController.fetchedResultsController.delegate = self
}
// For updating the table when its content changes - NSFetchedResultsControllerDelegate
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.namesTable.reloadData()
}
/* Other functions, etc, etc.... */
}
This arrangement works fine: when I add/remove entries to/from the table, the table reloads.
But I don't think that this is the right way of doing it, since the entire table is re-loaded every time when any change occurs. Seems like an overkill.
After looking online for several hours, I am not coming with anything. Can anyone point me to the right way of doing this? or am I doing it right?
Although there is nothing "wrong" with reloadData() it is unnecessary to call it every time one object changes, as you stated. You can update only the content you want by implementing the other NSFetchedResultsControllerDelegate functions and using beginUpdates() and endUpdates() like so:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case NSFetchedResultsChangeType.Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case NSFetchedResultsChangeType.Move:
break
case NSFetchedResultsChangeType.Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
}
}