NSFetchedResultsControllerDelegate method not called when switching tab - ios

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 NSFetchedResultsControllerDelegatewith a fetch method. But whenever I add or insert an item to the context on the first tab and switch to second tab, FRC delegate methods are not getting called. But when i implement the same methods on the first tab I can see them being call when I made a change to 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 {
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]
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
print("TableView endupdates")
tableView.endUpdates()
}
}

It looks like your fetchedResultsController is updating gamesMo, but your tableView is looking at arrayOfGamesModel. But arrayOfGamesModel is never updated.
You can either change your tableView methods to look at gamesMo, or change your fetchedResultsController to update arrayOfGamesModel.

Related

Fetched Results Controller doesn't update table at runtime

I try to use fetched results controller with swift 3, but it doesn't work. I saw, that my entity was added to Core Data with my own fetch request, but frc didn't saw them. If I restart my app, new elements will appear in table.
Creation of FRC:
func getFRCForChats() -> NSFetchedResultsController<Conversation>{
var fetchedResultsController: NSFetchedResultsController<Conversation>
let fetchRequest: NSFetchRequest<Conversation> = Conversation.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "conversationID", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController<Conversation>(fetchRequest:
fetchRequest, managedObjectContext: container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultsController
}
Using of FRC:
var fetchedResultsController: NSFetchedResultsController<Conversation>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
coreDataService = CoreDataService()
fetchedResultsController = coreDataService.getFRCForChats()
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(!fetchedResultsController.sections!.isEmpty) {
if let sectionInfo = fetchedResultsController.sections?[section]{
return sectionInfo.numberOfObjects
} else { print("Unexpected Section") }
}
return 0
}
func numberOfSections(in tableView: UITableView) -> Int {
if let count = fetchedResultsController.sections?.count {
return count
} else {
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatCell", for: indexPath) as!ChatTableViewCell
//cell.name.text = cells[indexPath.row]
let conversation = fetchedResultsController.object(at: indexPath)
if let participants = conversation.participants as? Set<User> {
conversation.managedObjectContext?.performAndWait {
for user in participants{
cell.name.text = user.name
break
}
}
}
return cell
}
Adding new entity:
var k = 2
#IBAction func createConversation(_ sender: Any) {
coreDataService.insertConversation(id: k)
k += 1
}
func insertConversation(id: Int) {
container.performBackgroundTask { (context) in
let user = User.findOrInsertUser(id: id, name: "Andrew", mobilePhone: 234567, avatar: nil, inContext: context)
_ = Conversation.findOrInsertConversation(id: id + 100, summa: Double(12+id), users: [user], transactions: [], inContext: context)
context.saveThrows()
}
let request: NSFetchRequest<Conversation> = Conversation.fetchRequest()
container.viewContext.perform {
if let results = try? self.container.viewContext.fetch(request) {
print("\(results.count) TweetMs")
for result in results{
print(result.conversationID, result.summa)
}
}
}
}
Delegate:
extension ChatsViewController: NSFetchedResultsControllerDelegate {
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 .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
case .move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex),
with: .automatic)
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex),
with: .automatic)
case .move, .update: break
}
}
}
Edit: If I call performFetch() after every editing of context, table reloaded without calling delegate.
As Apple Docs states:
automaticallyMergesChangesFromParent
A Boolean value that indicates whether the context automatically merges
changes saved to its persistent store coordinator or parent context.
Try setting it to true
container.viewContext.automaticallyMergesChangesFromParent = true
At least that solved similar problem for me.

Swift 3 Sectioned NSFetchResultController

I'm trying to get sections to work in my NSFetchResultController (Using Swift 3). The sections are of a calculated property of a NSManagedObject. The sections are displayed just fine. But every time I try to add a new Object which would generate a new section the app crashes with the following error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 4. The number of rows contained in an existing section after the update (1) 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 (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
The FetchResultController part of my TableViewController looks as follows:
lazy var fetchedResultsController: NSFetchedResultsController<Package> = {
let fetchRequest = NSFetchRequest<Package>(entityName:"Package")
let sectionDescriptor = NSSortDescriptor(key: "sectionTitle", ascending: true)
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.sortDescriptors = [sectionDescriptor , sortDescriptor]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "sectionTitle", cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
managedObjectContext = appDelegate.persistentContainer.viewContext
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("\(fetchError), \(fetchError.userInfo)")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Package")
do {
try managedObjectContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
The TableView Data Source part of my TableViewController looks as follows:
override func numberOfSections(in tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "PackageTableCell"
let cell: PackageTableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! PackageTableViewCell
let package = fetchedResultsController.object(at: indexPath)
cell.labelId.text = String(describing: package.packageId)
cell.labelTime.text = DateFormatter.localizedString(from: package.deliveryTime as! Date, dateStyle: DateFormatter.Style.none, timeStyle: DateFormatter.Style.short)
cell.labelWeight.text = String(describing: package.weight)
cell.labelBatch.text = String(describing: package.batch!.batchId)
cell.labelRecepients.text = String(describing: package.recepients)
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let package: Package = fetchedResultsController.object(at: indexPath)
managedObjectContext.delete(package)
}
}
The FetchResultController Delegate part of my TableViewController looks as follows:
func controllerWillChangeContent() {
tableView.beginUpdates()
}
func controllerDidChangeContent() {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
case .delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .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:
let cell = tableView.cellForRow(at: indexPath!) as! PackageTableViewCell
configureCell(cell: cell, atIndexPath: indexPath! as NSIndexPath)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
Then In my AddViewController I save the object as shown below:
#IBAction func savePressed(_ sender: UIBarButtonItem) {
if mode == .add {
let entity = NSEntityDescription.entity(forEntityName: "Package", in: self.managedObjectContext)
package = Package(entity: entity!, insertInto: self.managedObjectContext)
let packageId: Int = UserDefaults.standard.integer(forKey: "packageId")
package?.packageId = Int32(packageId)
UserDefaults.standard.set((packageId +1), forKey: "packageId")
package?.createdAt = NSDate()
package?.location = mapView.userLocation.location! as CLLocation
}
let calendar = NSCalendar.current
let selectedDate = pickerDate.date
let selectedTime = pickerTime.date
var deliveryTimeComponents = DateComponents()
deliveryTimeComponents.year = calendar.component(.year, from: selectedDate)
deliveryTimeComponents.month = calendar.component(.month, from: selectedDate)
deliveryTimeComponents.day = calendar.component(.day, from: selectedDate)
deliveryTimeComponents.hour = calendar.component(.hour, from: selectedTime)
deliveryTimeComponents.minute = calendar.component(.minute, from: selectedTime)
let deliveryTime = calendar.date(from: deliveryTimeComponents)
package?. deliveryTime = deliveryTime as NSDate?
package?.batch = batchPicker?.selectedBatch
package?.weight = Float(inputWeight.text!)!
package?.recepients = Int16(stepperRecepients.value)
package?.notes = textNotes.text!
do {
try package?.managedObjectContext?.save()
if mode == .edit {
packageTransferDelegate?. packageTransfer(transferPackage: package!)
}
dismiss(animated: true, completion: nil)
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
And finally my Package Class (Package+CoreDataClass)
import Foundation
import CoreData
public class Package: NSManagedObject {
var sectionTitle: String {
return DateFormatter.localizedString(from: self.deliveryTime! as Date, dateStyle: DateFormatter.Style.long, timeStyle: DateFormatter.Style.none)
}
}
Any tips on how I could resolve this problem would be very welcome. Also I'm fairly new to iOS development, so if you see something in my code which you don't like, feel free to write me about it 😉
I found the answer to my own question. I forgot to pass the NSFetchResultContoller into the controllerWillChangeContent and controllerDidChangeContent functions. Which lead to the crash.
I corrected the code as follows:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
Now everything seems to work just fine.

No NSArray being used, but getting "fatal error: NSArray element failed to match the Swift Array Element type"

I'm using Swift 3 in Xcode 8 beta 6, targeting iOS 10.0. I am implementing a simple UISearchController in a UITableView backed with an NSFetchedResultsController. I have two properties
var patients = [Patient]() // Assigned to fetchedResultsController.fetchedObjects when the fetch is performed, and when the moc is updated.
var searchResults = [Patient]()
In my updateSearchResults(for searchController: UISearchController) method, I do this:
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text {
self.searchResults = people.filter {
return $0.lastName!.localizedCaseInsensitiveContains(searchText)
}
Using breakpoints, I've identified that the code gets as far as the filter method, but doesn't enter it, failing with:
fatal error: NSArray element failed to match the Swift Array Element type
I've looked at a bunch of the other SO questions involving this error, but none have helped. I've also tried explicitly casting people in the updateSearchResults method, but no luck. Thoughts?
UPDATE Complete code for tableViewController and Patient subclass:
import UIKit
import CoreData
class PatientsListViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchResultsUpdating {
enum SegueIdentifier: String {
case showPatientDetail
}
//MARK: Properties
var managedObjectContext: NSManagedObjectContext!
var fetchedResultController: NSFetchedResultsController<Patient>!
var searchController: UISearchController!
var searchResults: [Patient] = []
var patients: [Patient] = []
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest: NSFetchRequest<Patient> = Patient.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "lastName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultController = NSFetchedResultsController<Patient>(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultController.delegate = self
do{
try fetchedResultController.performFetch()
patients = fetchedResultController.fetchedObjects!
}catch{
print(error)
}
//Add Search bar to the table header
searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
guard let numberOfSections = fetchedResultController.sections?.count else {
return 0
}
return numberOfSections
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// let section = fetchedResultController.sections![section]
// let numberOfRows = section.numberOfObjects
if searchController.isActive {
return searchResults.count
} else {
return fetchedResultController.sections![section].numberOfObjects
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PatientCell.reuseIdentifier, for: indexPath) as! PatientCell
let patient = (searchController.isActive) ? searchResults[indexPath.row] : fetchedResultController.object(at: indexPath)
cell.configure(with: patient)
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if searchController.isActive{
return false
}else{
return true
}
}
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) -> Void in
let patientToDelete = self.fetchedResultController.object(at: indexPath)
self.managedObjectContext.delete(patientToDelete)
do{
try self.managedObjectContext.save()
}catch{
print(error)
}
}
return [deleteAction]
}
// MARK: - FetchedResultsController delegate
// Notify the tableView that updates will begin
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
//Cover all cases of row changes like move, delete, insert, update
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let newIndexPath = newIndexPath{
tableView.insertRows(at: [newIndexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath{
tableView.deleteRows(at: [indexPath], with: .fade)
}
case .update:
if let indexPath = indexPath{
tableView.reloadRows(at: [indexPath], with: .fade)
}
case .move:
break
}
patients = controller.fetchedObjects as! [Patient]
}
// Notify the tableView that updates are done
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
//Pass Patient to PatientDetailViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier.flatMap(SegueIdentifier.init) else { return }
switch identifier {
case .showPatientDetail:
guard let indexPath = tableView.indexPathForSelectedRow else {
fatalError("No row selected in tableView")
}
let destinationController = segue.destination as! PatientDetailViewController
destinationController.patient = (searchController.isActive) ? searchResults[indexPath.row] : fetchedResultController.object(at: indexPath)
}
}
//Implement Search Bar
func filterContent(for searchText:String) {
searchResults = patients.filter( { patient -> Bool in
let nameMatch = patient.lastName?.localizedCaseInsensitiveContains(searchText)
return nameMatch != nil
})
}
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text {
filterContent(for: searchText)
tableView.reloadData()
}
}
}
PATIENT:
#objc(Patient)
public class Patient: NSManagedObject {
}
extension Patient {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Patient> {
return NSFetchRequest<Patient>(entityName: "Patient");
}
#NSManaged public var address: String?
#NSManaged public var dateOfBirth: String?
#NSManaged public var firstName: String?
#NSManaged public var gender: String?
#NSManaged public var lastName: String?
}

Cells become blank when scrolling tableview (swift)

I have a strange problem where my cells in the tableview are not consistent. Sometimes they will show as a blank cell and other times they will load with the correct data. See the GIF below.
Notice the blank cell in section 1 changes each time.
I also have this problem when adding new cells to the tableview, but closing and reopening the app always fixes it. It just doesn't load correctly when getting added... but sometimes it does load correctly. See GIF Below.
I've been recommended to use the reloadData(), but that doesn't seem to help anything at all. I'm hoping someone will see this that will know what to do.
See Code Below
Table View Controller: (Swift)
import UIKit
import CoreData
class ListItemsTVC: UITableViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
//var sequeItem: createItem?
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
frc = getFCR()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to perform inital fetch")
}
self.tableView.rowHeight = 62
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = frc.sections {
return sections.count
}
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 28
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = frc.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTVCell
let item = frc.objectAtIndexPath(indexPath) as! Items
cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
cell.itemName.text = item.name
cell.itemSection.text = item.section
cell.itemQty.text = "Qty: \(item.qty!)"
cell.itemSize.text = item.size
cell.itemPrice.text = floatToCurrency(Float(item.price!))
//cell.itemImage.image = UIImage(data: item.image!)
cell.itemID.text = String(item.id!)
return cell
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .Destructive, title: "Delete") { (action, indexPath) in
let item = self.frc.objectAtIndexPath(indexPath) as! Items
let id = item.id!
let request = self.fetchRequest()
let pred = NSPredicate(format: "%K == %#", "id",id)
request.predicate = pred
var fetchResults = [AnyObject]()
do {
fetchResults = try self.moc.executeFetchRequest(request)
} catch {
fatalError("Fetching Data to Delete Failed")
}
self.moc.deleteObject(fetchResults[0] as! NSManagedObject)
fetchResults.removeAtIndex(0)
do {
try self.moc.save()
} catch {
fatalError("Failed to Save after Delete")
}
}
return [delete]
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
/*case NSFetchedResultsChangeType.Update:
tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break*/
default:
print("Default in didChangeSection was called")
break
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
break
default:
print("Default in didChangeObject was called")
break
}
}
// MARK: - Custom Functions
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Items")
let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]
return fetchRequest
}
func getFCR() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)
return frc
}
func floatToCurrency(flt: Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return String(formatter.stringFromNumber(flt)!)
}
}
Add Button View Controller: (Swift)
import UIKit
import CoreData
class AddItemListVC: UIViewController, NSFetchedResultsControllerDelegate {
// MARK: - Constants and Variables
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var sendItem: Items?
// MARK: - App loading Functions
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Outlets and Actions
#IBAction func addItem(sender: AnyObject) {
let entityDesc = NSEntityDescription.entityForName("Items", inManagedObjectContext: moc)
let item = Items(entity: entityDesc!, insertIntoManagedObjectContext: moc)
if (NSUserDefaults.standardUserDefaults().objectForKey("nextItemID") == nil) {
NSUserDefaults.standardUserDefaults().setObject(1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
}
let id = NSUserDefaults.standardUserDefaults().integerForKey("nextItemID")
item.id = id
switch id {
case 1..<10:
item.name = "Item ID 00\(id)"
case 10..<100:
item.name = "Item ID 0\(id)"
default:
item.name = "Item ID \(id)"
}
item.brand = "Brand \(id)"
item.qty = 1
item.price = 0
item.size = "Size \(id)"
let sec: Int = Int(arc4random_uniform(UInt32(4 - 1))) + 1
item.section = "Section \(sec)"
item.isChecked = false
do {
try moc.save()
NSUserDefaults.standardUserDefaults().setObject(id + 1, forKey: "nextItemID")
NSUserDefaults.standardUserDefaults().synchronize()
} catch {
fatalError("New item save failed")
}
navigationController!.popViewControllerAnimated(true)
}
}
#Jason Brady, I have just downloaded your code.
There is no problem with you core data, array or table view.
When i run an app in iPhone 5 / iPhone 6 / iPhone 6 Plus with 8.1 it is working fine, none of cell or add button is getting hidden.
But with same devices with 9.2 there is a problem.
Solutions
(1) Custom cell with dequeueReusableCellWithIdentifier
let cell : ListItemsTVCell! = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTVCell
(2) DidselectedRowAtIndex - Here you will get information at cell selection, So data is going perfectly.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
print("Select")
let myCell = tableView.cellForRowAtIndexPath(indexPath) as! ListItemsTVCell
print(myCell.itemName.text)
}
(3) Problem is with AutoLayout, when i disabled it, all label went to -X positions, when i have aligned it properly using auto resizing, it is now working fine. ( See attached screen shot )
So you need to check with AutoLayout, why it is giving problem in iOS 9 and newer.
Refer link
I hope you can figure out and resolve further.
All the best.
Download
Instead of using
tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath)
try using
tableView.dequeueReusableCellWithIdentifier("listContentCell")

Passing core data from selected cell in tableview with certain attributes to another VC

So basically what I am trying to do is:
I have in my tableView a Title and SubTitle loaded from core data. When I select the cell, I want attributes that are not shown in that cell but stores in same entity to be passed to 3 different UITextField in my ViewController.
I have my prepareForSegue set up and ready, but I am missing what and how to send those attributes.
This is the code from my tableView
Updated Code with NSFetchedResultsController
class LedData: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierCell = "CellData"
// MARK: - IBOutlet
#IBOutlet var tableView: UITableView!
// MARK: - Variables
var managedObjectContext: NSManagedObjectContext!
lazy var fetchedResultsController: NSFetchedResultsController = {
// Initialize Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Ledinfo")
// Add Sort Descriptors
let sortDescriptor = NSSortDescriptor(key: "manufactor", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Initialize Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Configure Fetched Results Controller
fetchedResultsController.delegate = self
return fetchedResultsController
}()
// MARK: - VC Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("\(fetchError), \(fetchError.userInfo)")
}
}
// MARK: -
// MARK: Table View Data Source Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(ReuseIdentifierCell, forIndexPath: indexPath) as! CellData
// Configure Table View Cell
configureCell(cell, atIndexPath: indexPath)
return cell
}
func configureCell(cell: CellData, atIndexPath indexPath: NSIndexPath) {
// Fetch Record
let record = fetchedResultsController.objectAtIndexPath(indexPath)
// Update Cell
if let manufactorer = record.valueForKey("manufactor") as? String {
cell.makerName.text = manufactorer
}
if let model = record.valueForKey("model") as? String {
cell.modelName.text = model
}
}
// MARK: -
// MARK: Table View Delegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
// MARK: -
// MARK: Fetched Results Controller Delegate Methods
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:
if let indexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
break;
case .Update:
if let indexPath = indexPath {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CellData
configureCell(cell, atIndexPath: indexPath)
}
break;
case .Move:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
break;
}
}
}
This si what I have in my viewController that should receive.
var viaSegue1:String!
var viaSegue2:String!
var viaSegue3:String!
and
override func viewDidLoad()
{
super.viewDidLoad()
panelWidthTextField.text = viaSegue1
panelHightTextField.text = viaSegue2
panelPitchTextField.text = viaSegue3
}
Hope someone can help with this.
What you should do is use an NSFetchedResultsController to manage the data for your table.
A fetched results controller will save you a lot of inconvenience from having to manage your own array(s) of results from a fetch request.
When it's time to pass the information to your destination view controller, you can retrieve the other attributes right from the model object.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let viewController = segue.destinationViewController as? ViewController {
if let indexPath = tableView.indexPathForSelectedRow {
let record = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject // cast this as the type of your entity
viewController.panelWidth = record.panelWidth
viewController.panelHeight = record.panelHeight
viewController.panelPitch = record.panelPitch
}
}
}
}
Alternately, you could inject the managed object context and object's objectID, then fetch that particular object in your destination view controller.
Also, you make it appear like you're dealing with arrays of two different entities.
var manufactorer = [NSManagedObject]()
var model = [NSManagedObject]()
You should strongly type cast everything, so Swift knows exactly what type of managed object is in an array or set. If you're dealing with an array of Ledinfo types, you should declare it as such.
As an aside, you may want to use more descriptive names than viaSegue1, and ViewController. This makes your code easier to read, understand, and maintain.

Resources