I have application with UITableViewController, two UIViewController and Core Data. I want to make UISearchController for the first time. I looked on the other questions by this theme, but I have working incorrect UISearchController. When I write into searchBar, it give incorrect results. And I don't know how show them in the table view cell. Please help me.
My code in the UITableViewController
var resultSearchController: UISearchController!
var searchPredicate: NSPredicate?
var filteredObjects: [Note]? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
fetchedResultsController = NSFetchedResultsController(fetchRequest: allEmployeesFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: "mynote", cacheName: "mynote") // both mynote
fetchedResultsController?.delegate = self
fetchedResultsController?.performFetch(nil)
// UISearchController setup
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.delegate = self
controller.searchResultsUpdater = self
controller.hidesNavigationBarDuringPresentation = false
controller.dimsBackgroundDuringPresentation = true
controller.searchBar.sizeToFit()
controller.searchBar.delegate = self
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.tableView.reloadData()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = self.resultSearchController.searchBar.text
println(searchText)
if let searchText = searchText {
searchPredicate = NSPredicate(format: "mynote contains[c] %#", searchText)
filteredObjects = self.fetchedResultsController?.fetchedObjects?.filter() {
return self.searchPredicate!.evaluateWithObject($0)
} as! [Note]?
self.tableView.reloadData()
println(searchPredicate)
}
}
and
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?.count ?? 0
} else {
return 1
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0 //0 default
} else {
return filteredObjects?.count ?? 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellNote", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
if var cellContact = fetchedResultsController?.objectAtIndexPath(indexPath) as? Note {
cell.textLabel?.text = cellContact.mynote
cell.detailTextLabel?.text = cellContact.mytime
return cell
}
}
return cell
}
My core data class has
import Foundation
import CoreData
#objc(Note)
class Note: NSManagedObject {
#NSManaged var mynote: String
#NSManaged var mytime: String
}
I have a class that I use for multiple queries where I just pass a predicate, sometimes a sort and returns me an array of objects of the entity class.
class QueryDatabase: NSObject {
private var _managedObjectContext: NSManagedObjectContext?
init(managedObjectContext: NSManagedObjectContext){
super.init()
_managedObjectContext = managedObjectContext
}
func QueryTable(entityName: String, sortBy: String?, ascending: Bool, searchPredicate: NSPredicate?) -> AnyObject?{
if _managedObjectContext != nil {
let fetchRequest = NSFetchRequest(entityName: entityName)
if(sortBy != nil){
let sort = NSSortDescriptor(key: sortBy!, ascending: ascending)
fetchRequest.sortDescriptors = [sort]
}
if(searchPredicate != nil){
fetchRequest.predicate = searchPredicate!
}
var err: NSError?
if let fetchResults = _managedObjectContext!.executeFetchRequest(fetchRequest, error: &err){
return fetchResults
}else{
NSLog("ERROR:\(err?.description)")
}
}
return nil
}
}
I have used it in conjunction with a searchBar but in my case my predicate adds another modifier [cd] on its definition:
func findPricesBySearchCriteria(searchCriteria: String) -> [Prices]{
if(_managedObjectContext != nil){
let queryDatabase = QueryDatabase(managedObjectContext: _managedObjectContext!)
if let results = queryDatabase.QueryTable("Prices", sortBy: "item_description", ascending: true, searchPredicate: NSPredicate(format: "item_description contains[cd] %#", searchCriteria)) as? [Prices]{
arrSearch = results
}
}
return arrSearch
}
I found a solve, I changed my UITableViewController
class FirstTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate, UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate
{
#IBAction func addNew(sender: UIBarButtonItem) {
}
let managedObjectContext: NSManagedObjectContext? = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
var fetchedResultsController: NSFetchedResultsController?
// MARK: - setting for search controller
var searchController: UISearchController!
var searchPredicate: NSPredicate!
var filteredObjects: [Note]? = nil
var filteredDataFromObjects = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
fetchedResultsController = NSFetchedResultsController(fetchRequest: allEmployeesFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: "mynote", cacheName: "mynote") // both mynote
fetchedResultsController?.delegate = self
fetchedResultsController?.performFetch(nil)
self.searchController = ({
var controllerS = UISearchController(searchResultsController: nil)
controllerS.delegate = self
controllerS.searchBar.delegate = self
controllerS.searchResultsUpdater = self
controllerS.searchBar.sizeToFit()
controllerS.dimsBackgroundDuringPresentation = false
controllerS.hidesNavigationBarDuringPresentation = false
// self.definesPresentationContext = true
self.tableView.tableHeaderView = controllerS.searchBar
return controllerS
})()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredDataFromObjects.removeAll(keepCapacity: false)
var searchText = searchController.searchBar.text
if var searhText = searchText {
searchPredicate = NSPredicate(format: "mynote contains[c] %#", searchText)
filteredObjects = self.fetchedResultsController?.fetchedObjects?.filter() {
return self.searchPredicate.evaluateWithObject($0)
} as! [Note]?
self.tableView.reloadData()
println("Search Predicate \(searchPredicate)")
}
}
func allEmployeesFetchRequest() -> NSFetchRequest {
var fetchRequest = NSFetchRequest(entityName: "Note")
let sortDescriptor = NSSortDescriptor(key: "mytime", ascending: false) // change on date
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchBatchSize = 20
return fetchRequest
}
//MARK: UITableView Data Source and Delegate Functions
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?.count ?? 0 //after 0
} else {
return 1 ?? 0
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchPredicate == nil {
return fetchedResultsController?.sections?[section].numberOfObjects ?? 0 //0 default
} else {
return filteredObjects?.count ?? 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellNote", forIndexPath: indexPath) as! UITableViewCell
if searchPredicate == nil {
if var cellContact = fetchedResultsController?.objectAtIndexPath(indexPath) as? Note {
cell.textLabel?.text = cellContact.mynote
cell.detailTextLabel?.text = cellContact.mytime
}
} else {
if var textFromFiltered = filteredObjects?[indexPath.row] {
cell.textLabel?.text = textFromFiltered.mynote
cell.detailTextLabel?.text = textFromFiltered.mytime
}
}
return cell
}
//MARK: NSFetchedResultsController Delegate Functions
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.searchResultsUpdater as! FirstTableViewController).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:
break
case NSFetchedResultsChangeType.Update:
break
default:
break
}
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
}
switch editingStyle {
case .Delete:
managedObjectContext?.deleteObject(fetchedResultsController?.objectAtIndexPath(indexPath) as! Note)
managedObjectContext?.save(nil)
case .Insert:
break
case .None:
break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
// I made test which controller is need to show
var tableView = UITableView()
if searchPredicate == nil {
tableView = self.tableView
} else {
tableView = (searchController.searchResultsUpdater as! FirstTableViewController).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: newIndexPath!) 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.searchResultsUpdater as? FirstTableViewController)?.tableView.beginUpdates()
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
if searchPredicate == nil {
tableView.endUpdates()
} else {
(searchController.searchResultsUpdater as? FirstTableViewController)?.tableView.endUpdates()
}
}
#IBAction func unwindToFirst(segue: UIStoryboardSegue) {
}
// for sent data in detail controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
if searchPredicate == nil {
let object = self.fetchedResultsController!.objectAtIndexPath(indexPath) as! NSManagedObject
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController //DetailViewController
controller.detailItem = object
} else {
let object = filteredObjects![indexPath.row] as Note
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
}
}
self.searchController.active = false
}
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let object = self.fetchedResultsController!.objectAtIndexPath(indexPath) as! NSManagedObject
cell.textLabel!.text = object.valueForKey("mynote")!.description
}
// MARK: - UISearch functions
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResultsForSearchController(searchController)
}
func didDismissSearchController(searchController: UISearchController) {
filteredObjects = nil
searchPredicate = nil
tableView.reloadData()
}
}
Related
My application has two tab bars. The first one presents a list of games added on view controller and save them on the core data database. Switching on the second tab/view reads from the database and presents it inside a table view. I implemented the NSFetchedResultsControllerDelegate with a fetch method. When I add the first item to the context on the first tab and switch to second tab, FRC delegate methods (controllerWillChangeContent(_:), controller(_:didChange:at:for:newIndexPath:), controllerDidChangeContent(_:)) are not getting called and the table view is empty while I can see arrayOfGamesCount = 1. But when I add a second item, I can see all FRC delegate methods are getting call when I switch to second tab bar. And TableView display one rows while arrayOfGamesCount = 2
The first tab bar have 2 view controllers.(AddGameViewController and WelcomeViewController) AddGameVC is used to grab data from textfields and send it to welcomeVC.
import UIKit
import CoreData
class WelcomeViewController: UIViewController,SendGameDataDelegate, UIAdaptivePresentationControllerDelegate {
var games : [Game]? = []
var gamesMo: [GameMo]? = []
var gamed: GameMo?
var game : Game?
func ShouldSendGame(game: Game) {
self.game = game
print("\(game)")
games?.append(game)
}
#IBAction func endWLButton(_ sender: UIButton) {
saveDataToCoreData()
print("number of games from gamesMoCount is \(gamesMo?.count ?? 0)")
games?.removeAll()
reloadCollectionViewData()
}
func saveDataToCoreData (){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
gamed = GameMo(context: appDelegate.persistentContainer.viewContext)
if games != nil {
for game in games! {
gamed?.goal = Int32(game.goal ?? 0 )
gamed?.rivalGoal = Int32(game.rivalGoal ?? 0)
gamed?.shot = Int32(game.shots ?? 0)
gamed?.rivalShot = Int32(game.rivalGoal ?? 0)
gamed?.rivalCorners = Int32(game.rivalsCorner ?? 0)
gamed?.corners = Int32(game.corners ?? 0)
gamesMo?.append(gamed!)
}
print("Saving data to context ....")
appDelegate.saveContext()
}
}
}
}
extension WelcomeViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return games?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let gameIndex = games?[indexPath.row] {
let userGameScore = gameIndex.goal
let rivalGameScore = gameIndex.rivalGoal
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FormCell", for: indexPath) as? FormCollectionViewCell {
cell.setCell(userScores: userGameScore!, rivalScores: rivalGameScore! )
return cell
}
}
return UICollectionViewCell ()
}
}
The second tab bar have only one VC: AllWLeagueController used to display items from the the database.
import UIKit
import CoreData
class AllWLeagueController : UITableViewController {
var fetchRequestController : NSFetchedResultsController<GameMo>!
var arrayOfGamesModel : [[GameMo]]? = []
var gameMo: GameMo?
var gamesMo: [GameMo] = []
override func viewDidLoad() {
validation(object: arrayOfGamesModel)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchRequest()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("arrayOfGamesModelcount est \(arrayOfGamesModel?.count ?? 0)")
return arrayOfGamesModel?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let weekL = arrayOfGamesModel?[indexPath.row] {
if let cell = tableView.dequeueReusableCell(withIdentifier: "WL") as? AllWLeaguesTableViewCell {
let winCounts = WLManager.winCountMethod(from: weekL)
let lossCounts = WLManager.lossCountMethod(from:weekL)
cell.setOulet(win: winCounts, loss: lossCounts, rankName: rankString)
cellLayer(with: cell)
return cell
}
}
}
extension AllWLeagueController: NSFetchedResultsControllerDelegate {
func fetchRequest () {
let fetchRequest = NSFetchRequest<GameMo>(entityName: "Game")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "win", ascending: true)]
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate){
let context = appDelegate.persistentContainer.viewContext
// fetch result controller
fetchRequestController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchRequestController.delegate = self
do{
try fetchRequestController.performFetch()
if let fetchedObjects = fetchRequestController.fetchedObjects {
gamesMo = fetchedObjects
print("Fetech Request Activated")
print(gamesMo)
}
}catch{
fatalError("Failed to fetch entities: \(error)")
}
}
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
print("TableView beginupdates")
tableView.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 {
print("insert")
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath {
print("delete")
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath {
print("update")
tableView.reloadRows(at: [indexPath], with: .fade)
}
default:
tableView.reloadData()
}
if let fetchedObjects = controller.fetchedObjects {
gamesMo = fetchedObjects as! [GameMo]
print("we are about to append arrayOfGamesModel")
arrayOfGamesModel?.append(gamesMo)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
print("TableView endupdates")
tableView.endUpdates()
}
}
You are making a fatal mistake. In saveDataToCoreData only one instance is created and then it's being overwritten with the game data in each iteration of the array. So your array gamesMo may contain multiple items but it's always the same instance and only one instance is saved into the context.
Replace saveDataToCoreData with
func saveDataToCoreData (){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let games = games else { return }
for game in games {
let newGame = GameMo(context: appDelegate.persistentContainer.viewContext)
newGame.goal = Int32(game.goal ?? 0 )
newGame.rivalGoal = Int32(game.rivalGoal ?? 0)
newGame.shot = Int32(game.shots ?? 0)
newGame.rivalShot = Int32(game.rivalGoal ?? 0)
newGame.rivalCorners = Int32(game.rivalsCorner ?? 0)
newGame.corners = Int32(game.corners ?? 0)
gamesMo.append(newGame)
}
print("Saving data to context ....")
appDelegate.saveContext()
}
Another bad practice is to create new fetch results controllers in viewWillAppear. It's highly recommended to create one controller as lazy instantiated property – as well as the managed object context – for example
lazy var context : NSManagedObjectContext = {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}()
lazy var fetchRequestController : NSFetchedResultsController<GameMo> = {
let fetchRequest = NSFetchRequest<GameMo>(entityName: "Game")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "win", ascending: true)]
// fetch result controller
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
do {
try frc.performFetch()
if let fetchedObjects = frc.fetchedObjects {
self.gamesMo = fetchedObjects
print("Fetech Request Activated")
print(gamesMo)
}
} catch{
fatalError("Failed to fetch entities: \(error)")
}
return frc
}()
Force unwrapping AppDelegate is perfectly fine. Your app won't even launch if AppDelegate was missing.
I recommend also to use less ambiguous variable names. games, gamesMo, gamed and game look very similar and can cause confusion.
Im getting user's input and creating CoreData entity for it, and put in to the tableView, but before i need to designate some properties for entity by making a couple network requests. And there is a problem. First entity is always nil (and create a empty cell for table view) but if i save context and open app again - here it is, entity is ok. If i create second (and so on) entity it works fine. Its also seems like table always create am empty cell for start and then delete it and create proper cell…
I cant get why completion handlers doesn't work properly and network requests makes asynchronously. Please help me to understand.
Creation metod starts in textFieldShouldReturn
import UIKit
import CoreData
class ViewController: UIViewController, UITextFieldDelegate {
var managedObjectContext: NSManagedObjectContext?
lazy var fetchedResultsController: NSFetchedResultsController<Word> = {
let fetchRequest: NSFetchRequest<Word> = Word.fetchRequest()
let createdSort = NSSortDescriptor(key: "created", ascending: false)
let idSort = NSSortDescriptor(key: "id", ascending: false)
fetchRequest.sortDescriptors = [createdSort, idSort]
var fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: AppDelegate.viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
private var hasWords: Bool {
guard let fetchedObjects = fetchedResultsController.fetchedObjects else { return false }
return fetchedObjects.count > 0
}
let creator = WordManager.shared
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var messageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
managedObjectContext = AppDelegate.viewContext
fetchWords()
updateView()
}
func updateView() {
tableView.isHidden = !hasWords
messageLabel.isHidden = hasWords
}
............
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let managedObjectContext = managedObjectContext else { return false }
guard let title = textField.text, !title.isEmpty, !title.hasPrefix(" ") else {
showAllert(title: "Warning!", message: "Title may not to be nil!")
return false
}
let word = Word(context: managedObjectContext)
guard let fetchedWordsCount = fetchedResultsController.fetchedObjects?.count else { return false }
creator.createWord(title: title, counter: fetchedWordsCount, word: word)
self.updateView()
textField.text = nil
return true
}
func fetchWords() {
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Cant fetch words. Error \(error.localizedDescription)")
}
}
}
Creating entity
import CoreData
import UIKit
class WordManager {
static var shared = WordManager()
private init() {}
private var api = DictionaryAPI()
private var api2 = MeaningAPI()
func createWord(title: String, counter: Int, word: Word) {
self.api.fetch(word: title, completion: { translation in
self.api2.fetchTranscription(word: title, completion: { transcription in
word.id = Int16(counter) + 1
word.title = title
word.translation = translation
word.transcription = "[\(transcription)]"
word.created = NSDate()
})
})
}
}
Nothing interesting in tableView
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
guard let sections = fetchedResultsController.sections else { return 0 }
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let section = fetchedResultsController.sections?[section] else { return 0 }
return section.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mainCell", for: indexPath) as! MainCell
let word = fetchedResultsController.object(at: indexPath)
cell.configure(with: word)
return cell
}
}
Configure cell method
func configure(with word: Word) {
titleField.text = word.title
translationField.text = word.translation
transcriptionLabel.text = word.transcription
totalCounter.text = "\(word.showCounter)"
correctCounter.text = "\(word.correctCounter)"
wrongCounter.text = "\(word.wrongCounter)"
}
Fetched Results Delegate
extension ViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
updateView()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .delete:
guard let indexPath = indexPath else { return }
tableView.deleteRows(at: [indexPath], with: .fade)
case .insert:
guard let indexPath = newIndexPath else { return }
tableView.insertRows(at: [indexPath], with: .fade)
case .move:
guard let indexPath = indexPath else { return }
tableView.deleteRows(at: [indexPath], with: .fade)
guard let newIndexPath = newIndexPath else { return }
tableView.insertRows(at: [newIndexPath], with: .fade)
default:
break
}
}
}
Problem:
The data you have in core data is not in a state that is ready to be shown in the app.
The NSFetchedResultsController just happens to pick this data and shows it in the app.
Approach:
When a user creates a records, the user wants to see the changes immediately in the app and shouldn't have to wait for the app to update in the server to see the changes. So a user would still be able to create records even when there is no network connectivity.
Create a child context from view context
Use a background thread to upload to the server and when update records on to the child context.
Save child context only when the data is in a consistent state.
NSFetchedResultsController should be using the view context so would be unaware of the child context records.
When you save the child context, the NSFetchedResultsController would pick these records.
I'm creating a contacts app, so far I've successfully managed to save items to my tableview. I have a search bar and I want to filter out my cells by first name, I know since I'm working in Core Data I'll have to use fetchResultsController and NSPredicate. I'm having trouble figuring this stuff all out, maybe someone can help me out?
Also here is my Core Data entity, just in case.
Entity: Contact
Attributes:
firstName ,String
lastName, String
dateOfBirth, String
phoneNumber, String
zipCode, String
I know some of the code may be incomplete, but I just need direction on where to take this. I just want the user to type a name and it will filter the cells by first name. Let me know if there is more information you need.
Now here is the code in my ContactsTableVC:
import UIKit
import CoreData
class ContactsTableVC: UITableViewController, UISearchBarDelegate, NSFetchedResultsControllerDelegate {
#IBOutlet weak var searchBar: UISearchBar!
var isFiltered: Bool = false
//Holds the core data model
var persons: [Person] = []
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
self.tableView.backgroundColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0)
fetch()
self.tableView.reloadData()
}
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
// MARK: - Searchbar
//add fetchrequest to did ebgin editing
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
if(searchBar.text == "") {
isFiltered = false
} else {
isFiltered = true
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filter(text: searchText)
}
// MARK: - Fetchresults controller / filtering data
func filter(text: String) {
//Create fetch request
let fetchRequest = NSFetchRequest<Person>()
// guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } replaced with getcontext
// let managedObjectContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Contact", in: getContext())
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: false)
let sortDescriptors: [Any] = [sortDescriptor]
fetchRequest.sortDescriptors = sortDescriptors as? [NSSortDescriptor] ?? [NSSortDescriptor]()
if(text.characters.count > 0) {
let predicate = NSPredicate(format: "(firstName CONTAINS[c] %#)", text)
fetchRequest.predicate = predicate
}
let loadedEntities: [Person]? = try? getContext().fetch(fetchRequest)
filteredContacts = [Any](arrayLiteral: loadedEntities) as! [Person]
self.tableView.reloadData()
}
// MARK: - Data Source
func fetch() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Contact")
do {
persons = try managedObjectContext.fetch(fetchRequest) as! [Person] //NSManagedObject
} catch let error as NSError {
print("Could not fetch. \(error)")
}
}
func save(firstName: String, lastName: String, dob: String, phoneNumber: String, zipCode: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName:"Contact", in: managedObjectContext) else { return }
let person = NSManagedObject(entity: entity, insertInto: managedObjectContext)
person.setValue(firstName, forKey: "firstName")
person.setValue(lastName, forKey: "lastName")
person.setValue(dob, forKey: "dateOfBirth")
person.setValue(phoneNumber, forKey: "phoneNumber")
person.setValue(zipCode, forKey: "zipCode")
do {
try managedObjectContext.save()
self.persons.append(person as! Person) //previously just contact, no casting!
} catch let error as NSError {
print("Couldn't save. \(error)")
}
}
func update(indexPath: IndexPath, firstName: String, lastName: String, dob: String, phoneNumber: String, zipCode: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let contact = persons[indexPath.row]
contact.setValue(firstName, forKey: "firstName")
contact.setValue(lastName, forKey: "lastName")
contact.setValue(dob, forKey: "dateOfBirth")
contact.setValue(phoneNumber, forKey: "phoneNumber")
contact.setValue(zipCode, forKey: "zipCode")
do {
try managedObjectContext.save()
persons[indexPath.row] = contact
} catch let error as NSError {
print("Couldn't update. \(error)")
}
}
func delete(_ contact: NSManagedObject, at indexPath: IndexPath) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
managedObjectContext.delete(contact)
persons.remove(at: indexPath.row)
//Always remember to save after deleting, updates Core Data
do {
try managedObjectContext.save()
} catch {
print("Something went wrong \(error.localizedDescription)")
}
}
// MARK: - Table View Setup
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return persons.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell", for: indexPath) as? PersonsCell
let person = persons[indexPath.row]
cell?.firstName?.text = person.value(forKey:"firstName") as? String
cell?.lastName?.text = person.value(forKey:"lastName") as? String
cell?.dob?.text = person.value(forKey:"dateOfBirth") as? String
cell?.phoneNumber?.text = person.value(forKey:"phoneNumber") as? String
cell?.zipCode?.text = person.value(forKey:"zipCode") as? String
return cell!
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// MARK: - Navigation
#IBAction func unwindToContactsList(segue:UIStoryboardSegue) {
if let viewController = segue.source as? AddContactVC {
guard let _firstName: String = viewController.firstNameLbl.text,
let _lastName: String = viewController.lastNameLbl.text,
let _dob: String = viewController.dateOfBirthLbl.text,
let _phoneNumber: String = viewController.phoneNumberLbl.text,
let _zipCode: String = viewController.zipCodeLbl.text
else { return }
if _firstName != "" && _lastName != "" && _dob != "" && _phoneNumber != "" && _zipCode != "" {
if let indexPath = viewController.indexPathForContact {
update(indexPath: indexPath, firstName: _firstName, lastName: _lastName, dob: _dob, phoneNumber: _phoneNumber, zipCode: _zipCode)
print("Any updates?")
} else {
save(firstName: _firstName, lastName: _lastName, dob: _dob, phoneNumber: _phoneNumber, zipCode: _zipCode)
print("added to tableview") //this runs twice for some reason...
}
}
tableView.reloadData()
} else if let viewController = segue.source as? EditContactVC {
if viewController.isDeleted {
guard let indexPath: IndexPath = viewController.indexPath else { return }
let person = persons[indexPath.row]
delete(person, at: indexPath)
tableView.reloadData()
}
}
}
}
Here is a sample code to achieve your goal with NSFetchedResultsController. I omitted some irrelevant codes.
class ContactViewController: UITableViewController {
let fetchedResultsController: NSFetchedResultsController<Contact>!
func searchTextFieldDidEditingChanged(_ textField: UITextField) {
let text = textField.text ?? ""
refetch(with: text)
}
// The key is you need change the predicate when searchTextField's
// value changed, and invoke proformFetch() again
func refetch(with text: String) {
let predicate = NSPredicate(format: "firstName CONTAINS %#", text)
fetchedResultsController.fetchRequest.predicate = predicate
do {
try self.fetchedResultsController.performFetch()
tableView.reloadData()
} catch let error as NSError {
loggingPrint("Error: \(error.localizedDescription)")
}
}
}
// MARK: - Table datasource
extension ContactViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections!.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell", for: indexPath) as? PersonsCell
let contact = fetchedResultsController.object(at: indexPath)
cell.contact = contact
return cell!
}
}
You can use the sortDescriptors property of NSPredicate to filter the results of your fetch request.
Check the link for more info:
How to sort a fetch in Core Data
I'm developing reminder app and I'm facing some issues, one of them is when user tap on one of the category the table shall be reloaded based on the chosen category; the task name and category are reloaded correctly with no issues, but the time and date are not filtered as shown in photos below
I believe there's something wrong in code below, how I can solve this issue?
ReminderTable.swift
class ReminderTable: UITableViewController,NSFetchedResultsControllerDelegate{
#IBOutlet weak var menuViewRelative: UIView!
#IBOutlet weak var categroyFilter:UISegmentedControl!
var reminders:[ReminderData] = []
var fetchResultController:NSFetchedResultsController!
let reminderFetch = NSFetchRequest(entityName: "Reminder")
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
title = "Reminder"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
let fetchRequest = NSFetchRequest(entityName: "Reminder")
let sortDescriptor = NSSortDescriptor(key:"time", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do {
try fetchResultController.performFetch()
reminders = fetchResultController.fetchedObjects as! [ReminderData]
} catch {
print(error)
}
}
loadData("All")
tableView.allowsSelection = false
self.navigationController!.toolbarHidden = false
}
override func viewDidDisappear(animated: Bool) {
categroyFilter.selectedSegmentIndex = 0
}
func loadData(category:String){
if category == "All"{
do{
let request = NSFetchRequest(entityName:"Reminder")
let sortDescriptor = NSSortDescriptor(key:"time", ascending: true)
request.sortDescriptors = [sortDescriptor]
reminders = try managedObjectContext.executeFetchRequest(request) as! [ReminderData]
self.tableView.reloadData()
}catch{
fatalError("error")
}
}else{
do{
let request = NSFetchRequest(entityName:"Reminder")
request.predicate = NSPredicate(format: "category = %#", category)
reminders = try managedObjectContext.executeFetchRequest(request) as! [ReminderData]
self.tableView.reloadData()
}catch{
fatalError("error")
}
}
self.tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationController?.hidesBarsOnSwipe = false
self.navigationController!.navigationBarHidden = false
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reminders.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let task = fetchResultController.objectAtIndexPath(indexPath)
as! ReminderData
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ReminderListCell
cell.name.text = reminders[indexPath.row].name
cell.category.text = reminders[indexPath.row].category
cell.date.text = task.stringForDate()
cell.time.text = task.stringForTime()
return cell
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
reminders.removeAtIndex(indexPath.row)
}
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let RemoveAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Remove",handler: { (action, indexPath) -> Void in
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
let remove = UIAlertController(title: "Are You Sure You Want Delete this Reminder ?", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
let Yes = UIAlertAction(title: "Yes", style: .Default) { (action) in
let reminderToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as! ReminderData
managedObjectContext.deleteObject(reminderToDelete)
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
remove.addAction(Yes)
let No = UIAlertAction(title: "No", style: .Destructive) { (action) in
}
remove.addAction(No)
self.presentViewController(remove, animated: true) {}
}
})
let editAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Edit",handler: { (action, indexPath) -> Void in
tableView.setEditing(false, animated: true)
let cell = tableView.cellForRowAtIndexPath(indexPath)
self.performSegueWithIdentifier("editReminder", sender: cell)
})
RemoveAction.backgroundColor = UIColor(red: 247/255, green: 93/255, blue: 89/255, alpha: 1)
editAction.backgroundColor = UIColor(red: 21/255, green: 137/255, blue: 255/255, alpha: 1)
return [RemoveAction,editAction]
}
#IBAction func unwindToHomeScreen(segue:UIStoryboardSegue) {
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
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)
case .Update:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
default: break
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "editReminder" {
// let task = segue.destinationViewController as! UINavigationController
let navController = segue.destinationViewController as! UINavigationController
let task = navController.topViewController as! Reminder
if let indexPath = tableView.indexPathForCell(sender as! ReminderListCell) {
task.reminders = reminders[indexPath.row]
}
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
if let _newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([_newIndexPath], withRowAnimation: .Fade)
}
case .Delete:
if let _indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([_indexPath], withRowAnimation: .Left)
}
case .Update:
if let _indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([_indexPath], withRowAnimation: .Fade)
}
default:
tableView.reloadData()
}
reminders = controller.fetchedObjects as! [ReminderData]
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
#IBAction func selectCategory(sender: UISegmentedControl) {
let selectedValue = sender.titleForSegmentAtIndex(sender.selectedSegmentIndex)
switch selectedValue!{
case "Task":
loadData(selectedValue!)
break
case "Medication":
loadData(selectedValue!)
break
case "Appointment":
loadData(selectedValue!)
break
default:
loadData(selectedValue!)
break
}
}
override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
var fixedFrame:CGRect = self.menuViewRelative.frame
fixedFrame.origin.y = 0 + scrollView.contentOffset.y
self.menuViewRelative.frame = fixedFrame
}
}
ReminderData.swift
class ReminderData : NSManagedObject {
#NSManaged var date: NSDate?
#NSManaged var name: String?
#NSManaged var category: String?
#NSManaged var time : NSDate?
func stringForDate() -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle
if let date = date {
return dateFormatter.stringFromDate(date)
} else {
return ""
}
}
func stringForTime() -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "hh:mm a"
if let time = time {
return dateFormatter.stringFromDate(time)
} else {
return ""
}
}
}
Replace
task.stringForDate()
task.stringForTime()
with
reminders[indexPath.row].stringForDate()
reminders[indexPath.row].stringForTime()
I apologize in advance if this is confusing. I'm new to Swift with no prior experience and learning.
Basically, I have a UITableView that pulls its data form another viewController with four text fields. The data is saved and added to the tableView (only displaying the two of the fields in the cell). When the cell is selected it segues back to the viewController and adds the data in the appropriate text fields. This also utilized CoreData and NSFetchResultsController. All of this is working properly so far, just wanted to give a little back ground on whats going on.
The Problem...
I am trying to get the tableView to display the data in two sections. I want the first section (section 0) to display the data added to the list created by adding the text fields on the viewController. However, the tableView adds first row as the first section (with the header correct) and then adds the second row (and on) as the second section (section 1)(with section header). I want to add all new items from the viewController to section 0, leaving section 1 blank for when I figure out how to cross items off section 0 and move to section 1 by selecting the row (Coming in the future).
I am new to this site as well and could not figure out how to post my code. If anyone needs to look through it you may have to walk me through adding it. Also, I have trouble converting Objective C to Swift please keep that in mind while answering. Thank you!! and I apologize for being difficult.
(tableView Controller "SList")
class ShoppingList: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
func itemFetchRequest() -> NSFetchRequest{
let fetchRequest = NSFetchRequest(entityName: "SList")
let primarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]
return fetchRequest
}
func getFetchRequetController() ->NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slitem", cacheName: nil)
return frc
}
#IBOutlet weak var tableView: UITableView!
#IBAction func AddNew(sender: AnyObject) {
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
//TableView Background Color
self.tableView.backgroundColor = UIColor.clearColor()
tableView.reloadData()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
self.tableView.separatorColor = UIColor.blackColor()
}
override func viewDidDisappear(animated: Bool) {
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
}
//TableView Data
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let managedObject:NSManagedObject = frc.objectAtIndexPath(indexPath) as! NSManagedObject
moc.deleteObject(managedObject)
do {
try moc.save()
} catch _ {
print("Failed to save.")
return
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let numberOfSections = frc.sections?.count
return numberOfSections!
}
//table section headers
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
let sectionHeader = "Items - #\(frc.sections![section].numberOfObjects)"
let sectionHeader1 = "Crossed Off Items - #\(frc.sections![section].numberOfObjects)"
if (section == 0) {
return sectionHeader
}
if (section == 1){
return sectionHeader1
}else{
return nil
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let items = frc.objectAtIndexPath(indexPath) as! SList
cell.backgroundColor = UIColor.clearColor()
cell.textLabel?.text = "\(items.slitem!) - Qty: \(items.slqty!)"
cell.textLabel?.font = UIFont.systemFontOfSize(23)
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.reloadData()
}
//segue to add/edit
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "edit" {
let cell = sender as! UITableViewCell
let indexPath = tableView.indexPathForCell(cell)
let SListController:SLEdit = segue.destinationViewController as! SLEdit
let items:SList = frc.objectAtIndexPath(indexPath!) as! SList
SListController.item = items
}
}
}
(ViewController "SLEdit")
class SLEdit: UIViewController {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var slitem: UITextField!
#IBOutlet weak var sldesc: UITextField!
#IBOutlet weak var slqty: UITextField!
#IBOutlet weak var slprice: UITextField!
var item: SList? = nil
override func viewDidLoad() {
super.viewDidLoad()
if item != nil{
slitem.text = item?.slitem
sldesc.text = item?.sldesc
slqty.text = item?.slqty
slprice.text = item?.slprice
}
// "x" Delete Feature
self.slitem.clearButtonMode = UITextFieldViewMode.WhileEditing
self.sldesc.clearButtonMode = UITextFieldViewMode.WhileEditing
self.slqty.clearButtonMode = UITextFieldViewMode.WhileEditing
self.slprice.clearButtonMode = UITextFieldViewMode.WhileEditing
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func dismissVC() {
navigationController?.popViewControllerAnimated(true)
}
#IBAction func saveButton(sender: AnyObject) {
if item != nil {
edititems()
} else {
createitems()
}
dismissVC()
}
func createitems() {
let entityDescription = NSEntityDescription.entityForName("SList", inManagedObjectContext: moc)
let item = SList(entity: entityDescription!, insertIntoManagedObjectContext: moc)
item.slitem = slitem.text
item.sldesc = sldesc.text
item.slqty = slqty.text
item.slprice = slprice.text
if slitem.text == nil{
createitems()
}else{
edititems()
}
do {
try moc.save()
} catch _ {
return
}
}
func edititems() {
item?.slitem = slitem.text!
item?.sldesc = sldesc.text!
item?.slqty = slqty.text!
item?.slprice = slprice.text!
do {
try moc.save()
} catch {
return
}
}
}
I think the problem lies in your FRC configuration:
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slitem", cacheName: nil)
Because sectionNameKeyPath is slitem, the FRC creates a new section for each different value of slitem.
If the slcross attribute is used to indicate that the item has been crossed off the list, specify that as the sectionNameKeyPath:
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slcross", cacheName: nil)
You will also need to modify the sorting of the fetch request (it MUST be sorted so that all items in a given section are together):
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]