NSFetchedResultsController not Updating UI (Swift) - 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()
}
}

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.

NSFetchedResultsController crash when Section index changes

I'm writing my app in Swift 3 (converted) in Xcode 8.
NSFetchedResultsController is causing a Serious Application Error for me.
My main table view is sectioned by a text identifier called "yearText" which is being set on any given Event record (NSManagedObject) when the user changes the "Event Date" with a date picker. When the picker is changed or dismissed, the year is stripped from the date, converted to text, and stored in the Event object. The managed object context is then saved.
If a date is picked for which there is already a section in existence (i.e. the year "2020") an error is thrown that says:
[error] error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
As long as the date chosen is not within a year that already has a section named after it, it all works fine.
Here is my relevant code for updating the database and tableview:
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
// Fetch the default object (Event)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName: "Event", in: managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 60
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext!, sectionNameKeyPath: "yearText", cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Implement error handling code here.
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
// MARK: - UITableViewDelegate
extension EventListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! EventCell
cell.isSelected = true
configureCell(withCell: cell, atIndexPath: indexPath)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! EventCell
cell.isSelected = false
configureCell(withCell: cell, atIndexPath: indexPath)
}
}
// MARK: - UITableViewDataSource
extension EventListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventCell", for: indexPath) as! EventCell
configureCell(withCell: cell, atIndexPath: indexPath)
return cell
}
func configureCell(withCell cell: EventCell, atIndexPath indexPath: IndexPath) {
// bunch of stuff to make the cell pretty and display the data
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = fetchedResultsController.managedObjectContext
context.delete(fetchedResultsController.object(at: indexPath) as! NSManagedObject)
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.name
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
// make the section header look good
view.tintColor = kWPPTintColor
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = kWPPDarkColor
header.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline)
}
}
// MARK: - NSFetchedResultsControllerDelegate
extension EventListViewController: NSFetchedResultsControllerDelegate {
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)
default:
return
}
}
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:
configureCell(withCell: tableView.cellForRow(at: indexPath!)! as! EventCell, atIndexPath: indexPath!)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
I hope you can offer me some suggestions. Thank you.
EDIT: Took out some code that was just getting in the way and revised .move to use .moveRow
EDIT 2: Added FRC generation code.
I met the same error when I update some properties on my Core Data managed objects.
Here is my controller func:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
self.tableView.reloadRows(at: [indexPath!], with: .fade)
case .move:
self.tableView.insertRows(at: [newIndexPath!], with: .fade)
self.tableView.deleteRows(at: [indexPath!], with: .fade)
}
}
Before I used newIndexPath for the update case, but I found this will cause some section rows mismatch issue when fetch result controller do some update action. Instead, using indexPath for update case is fine.

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

UISearchController returns only first results but Core Data has several results

I have UISearchController and it returns only first results but Core Data has several results.
I did many different variants but they didn't help me.
Else UISearchController returns incorrect results.
import UIKit
import Foundation
import CoreData
class SongTableVC: UITableViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating {
// MARK: - var and let
var appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
// MARK: - NSFetchedResultsController and its functions
var fetchedResultsController: NSFetchedResultsController!
func returnRequest() -> NSFetchRequest {
var fetchRequest = NSFetchRequest(entityName: "Song")
var sort = NSSortDescriptor(key: "songName", ascending: false)
fetchRequest.fetchBatchSize = 50
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = [sort]
return fetchRequest
}
// MARK: - UISearchController and its fucntions
var searchController: UISearchController!
var searchPredicate: NSPredicate!
var dataFiltered: [Song]? = nil
func updateSearchResultsForSearchController(searchController: UISearchController) {
var searchText = searchController.searchBar.text
searchPredicate = NSPredicate(format: "songName contains[c] %#", searchText)
dataFiltered = self.fetchedResultsController?.fetchedObjects?.filter(){
return self.searchPredicate!.evaluateWithObject($0)
} as! [Song]?
self.tableView.reloadData()
println(searchPredicate)
}
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResultsForSearchController(searchController)
}
func didDismissSearchController(searchController: UISearchController) {
searchPredicate = nil
dataFiltered = nil
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
fetchedResultsController = NSFetchedResultsController(fetchRequest: returnRequest(), managedObjectContext: context, sectionNameKeyPath: "songName", cacheName: "songName")
fetchedResultsController.delegate = self
fetchedResultsController.performFetch(nil)
searchController = ({
var controllerSearch = UISearchController(searchResultsController: nil)
controllerSearch.delegate = self
controllerSearch.searchBar.delegate = self
controllerSearch.searchBar.sizeToFit()
controllerSearch.definesPresentationContext = false // default false
controllerSearch.hidesNavigationBarDuringPresentation = true
controllerSearch.searchResultsUpdater = self
controllerSearch.dimsBackgroundDuringPresentation = false
self.tableView.tableHeaderView = controllerSearch.searchBar
return controllerSearch
})()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
if searchPredicate == nil {
return fetchedResultsController?.sections?.count ?? 0
} else {
return 1 ?? 0
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0
} else {
return dataFiltered?.count ?? 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("songID", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
if var dataForCell = fetchedResultsController.objectAtIndexPath(indexPath) as? Song {
cell.textLabel?.text = dataForCell.songName
cell.detailTextLabel?.text = dataForCell.songName
} else {
if var dataFilterForCell = dataFiltered?[indexPath.row] {
cell.textLabel?.text = dataFilterForCell.songName
cell.textLabel?.text = dataFilterForCell.songName
}
}
}
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
context.deleteObject(fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
context.save(nil)
} else if editingStyle == .Insert {
context.insertObject(fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
context.save(nil)
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsController as! SongTableVC).tableView
}
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:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Update:
break
default: break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsController as! SongTableVC).tableView
}
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertRowsAtIndexPaths([AnyObject](), withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Delete:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Move:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertRowsAtIndexPaths(NSArray(object: indexPath!) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Update:
tableView.cellForRowAtIndexPath(indexPath!)
break
default: break
}
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
if searchPredicate == nil {
tableView.beginUpdates()
} else {
(searchController.searchResultsController as? SongTableVC)?.tableView.beginUpdates()
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
if searchPredicate == nil {
tableView.endUpdates()
} else {
(searchController.searchResultsController as? SongTableVC)?.tableView.endUpdates()
}
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "add" {
searchController.active == false
}
}
}
I found a solution of my question
import UIKit
import Foundation
import CoreData
class SongTableViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating {
// MARK: - var and lets
var appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: context, sectionNameKeyPath: "nameSong", cacheName: "nameSong")
fetchedResultsController.delegate = self
fetchedResultsController.performFetch(nil)
self.navigationItem.leftBarButtonItem = self.editButtonItem()
searchController = ({
var controllerSearch = UISearchController(searchResultsController: nil)
controllerSearch.delegate = self
controllerSearch.searchBar.delegate = self
controllerSearch.hidesNavigationBarDuringPresentation = true
controllerSearch.definesPresentationContext = false
controllerSearch.dimsBackgroundDuringPresentation = false
controllerSearch.searchBar.sizeToFit()
controllerSearch.searchResultsUpdater = self
self.tableView.tableHeaderView = controllerSearch.searchBar
return controllerSearch
})()
//
println(path)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
searchPredicate = nil
filteredData = nil
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - NSFetchedResultsController and its functions
var fetchedResultsController: NSFetchedResultsController!
func fetchRequest() -> NSFetchRequest {
var fetchRequest = NSFetchRequest(entityName: "Song")
var sort = NSSortDescriptor(key: "nameSong", ascending: false)
fetchRequest.fetchBatchSize = 50
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = [sort]
return fetchRequest
}
// MARK: - UISearchController and its functions
var searchController: UISearchController!
var searchPredicate: NSPredicate!
var filteredData: [Song]? = nil
func updateSearchResultsForSearchController(searchController: UISearchController) {
var searchText = searchController.searchBar.text
if searchText != nil {
searchPredicate = NSPredicate(format: "nameSong contains[c] %#", searchText)
filteredData = fetchedResultsController.fetchedObjects!.filter() {
return self.searchPredicate.evaluateWithObject($0)
} as? [Song]
self.tableView.reloadData()
}
}
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResultsForSearchController(searchController)
}
func didDismissSearchController(searchController: UISearchController) {
searchPredicate = nil
filteredData = nil
self.tableView.reloadData()
}
// MARK: - Files from shared folder
var fileManager = NSFileManager.defaultManager()
var path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?.count ?? 0
} else {
return 1 ?? 0
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0
} else {
return filteredData?.count ?? 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("songID", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
if var dataForCell = fetchedResultsController?.objectAtIndexPath(indexPath) as? Song {
cell.textLabel?.text = dataForCell.nameSong
}
} else {
if var filteredSearch = filteredData?[indexPath.row] {
cell.textLabel?.text = filteredSearch.nameSong
}
}
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
context.deleteObject(fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
context.save(nil)
} else if editingStyle == .Insert {
context.insertObject(fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
context.save(nil)
}
}
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:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Update:
break
default: break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertRowsAtIndexPaths([AnyObject](), withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Delete:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Move:
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath!) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertRowsAtIndexPaths(NSArray(object: indexPath!) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Update:
tableView.cellForRowAtIndexPath(indexPath!)
break
default: break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the item to be re-orderable.
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
}