I'm attempting to use a fetchedResultsController to handle the results in my UITable.
It works initially when the program starts up. Then when I switch back to the inventory tab where my table is (for the viewToAppear again), this is when it crashes.
I'm getting a runtime crash error in my viewWillAppear() method of the window which has the table.
In particular it crashes on the Inventory+CoredataProperties.swift file on this line let characters = name!.characters.map { String($0) }
, but I suspect the error is somewhere else as this works initially so why not now on the 2nd reload?
Here is the function.
override func viewWillAppear(_ animated: Bool) { print("view appearing") //When the view appears its important that the table is updated. //Trigger Event on SearchBar in case returning from BarCode Scanner // self.searchBar:SearchBar textDidChange:recentlySearchedWord; //searchBar.performSelector(":textDidChange") //Perform another fetch again to get correct data~ do { //fetchedResultsController. //this will force setter code to run again. print("attempting fetch again, reset to use lazy init") fetchedResultsController = setFetchedResultsController() //sets it again so its correct. try fetchedResultsController.performFetch() } catch { print("An error occurred") } inventoryTable.reloadData()//this is important to update correctly for changes that might have been made }
The error occurs on the try fetchedResultsController.performFetch() statement. I'm getting a lot of errors before the actual crash occurs saying "API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x170265300, store PSC = 0x0). I've been refactoring my code to work with the new swift 3 standards I have a feeling I did something wrong or maybe something changed with how the fetched results controller works.
Any help is appreciated as to what could be the cause?
If you think I'm missing a file you need to see, just let me know and I'll add it to the relevant source code below.
POSSIBLE RELEVANT SOURCE CODE BELOW:
InventoryController.swift (Entire File)
import UIKit import CoreData import Foundation class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate { @available(iOS 2.0, *) //Create fetchedResultsController to handle Inventory Core Data Operations lazy var fetchedResultsController: NSFetchedResultsController<Inventory> = { return self.setFetchedResultsController() }() //Reference to search text for filtering var m_searchText = "" func setFetchedResultsController() -> NSFetchedResultsController<Inventory>{ let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let inventoryFetchRequest : NSFetchRequest<Inventory> = Inventory.fetchRequest() var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name. print("primarySortDescriptor...") if(g_appSettings[0].indextype=="numberfront"){ primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true) }else if(g_appSettings[0].indextype=="numberback"){ primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true) }else if(g_appSettings[0].indextype=="numberfourth"){ primarySortDescriptor = NSSortDescriptor(key: "barcodeFourth", ascending: true, selector: #selector(NSString.localizedCompare(_:))) } print("set primarySortDescriptor") //let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true) inventoryFetchRequest.sortDescriptors = [primarySortDescriptor] print("set sort descriptors to fetch request") var storefilter : Store? = nil var predicate: NSPredicate //Store should never be set to nil, the first store should always be selected by default. For fringe cases just in case ... support added so doesn't break if(g_appSettings[0].selectedStore != nil){ storefilter = g_appSettings[0].selectedStore predicate = NSPredicate(format: "store = %@", storefilter!) //default predicate assuming store is selected //However if search text is present then modify predicate if(m_searchText != ""){ predicate = NSPredicate(format: "store = %@ AND name contains[cd] %@ OR store = %@ AND barcode contains[cd] %@", storefilter!,m_searchText,storefilter!,m_searchText) } //This will ensure correct data relating to store is showing (and if any filters present, them as well) inventoryFetchRequest.predicate = predicate }else{ if(m_searchText != ""){ predicate = NSPredicate(format: "name contains[cd] %@ OR barcode contains[cd] %@",m_searchText,m_searchText) inventoryFetchRequest.predicate = predicate //This will ensure correct data relating to store is showing } } //default assume letter section var frc = NSFetchedResultsController( fetchRequest: inventoryFetchRequest, managedObjectContext: moc, sectionNameKeyPath: "lettersection", cacheName: nil) if(g_appSettings[0].indextype=="numberfront"){ frc = NSFetchedResultsController( fetchRequest: inventoryFetchRequest, managedObjectContext: moc, sectionNameKeyPath: "numbersection", cacheName: nil) }else if(g_appSettings[0].indextype=="numberback"){ frc = NSFetchedResultsController( fetchRequest: inventoryFetchRequest, managedObjectContext: moc, sectionNameKeyPath: "numberendsection", cacheName: nil) }else if(g_appSettings[0].indextype=="numberfourth"){ frc = NSFetchedResultsController( fetchRequest: inventoryFetchRequest, managedObjectContext: moc, sectionNameKeyPath: "numberfourthsection", cacheName: nil) } print("set the frc") frc.delegate = self return frc } @IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var inventoryTable: UITableView! // Start DEMO Related Code var numberIndex = ["0","1","2","3","4","5","6","7","8","9"] var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] var previousNumber = -1 //used so we show A,A, B,B, C,C etc for proper testing of sections func createInventoryDummyData(number: Int) -> Inventory{ let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let tempInventory = NSEntityDescription.insertNewObject(forEntityName: "Inventory", into: moc) as! Inventory if(number-1 == previousNumber){ tempInventory.name = "\(letterIndex[number-2])-Test Item # \(number)" previousNumber = -1//reset it again }else{ tempInventory.name = "\(letterIndex[number-1])-Test Item # \(number)" previousNumber = number //set previous letter accordingly } tempInventory.barcode = "00000\(number+1)00\(number)" //special exception to demo barcode reader if(number==5){ tempInventory.barcode = "0051111407592" } if(number==6){ tempInventory.barcode = "0036000291452" } tempInventory.barcodeReverse = String(tempInventory.barcode!.characters.reversed()) //Convert barcode into array of characters and take note if its size for indexing let bcArraySize = Int(tempInventory.barcode!.characters.count) - 1//for correct indexing var bcArray = tempInventory.barcode!.characters.map { String($0) } print(bcArray) print(bcArraySize) //Take the digits from the 4th one at a time and convert to strings concatenating as you go. let fourth = "\(bcArray[bcArraySize-3])"+"\(bcArray[bcArraySize-2])"+"\(bcArray[bcArraySize-1])"+"\(bcArray[bcArraySize])" print(fourth) //Finally convert that into a number again and set to barcodeFourth tempInventory.barcodeFourth = fourth print(tempInventory.barcodeFourth!) //tempInventory.barcodeFourth = //print(tempInventory.barcodeReverse) tempInventory.currentCount = 0 tempInventory.id = number as NSNumber? tempInventory.imageLargePath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png" tempInventory.imageSmallPath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png" tempInventory.addCount = 0 tempInventory.negativeCount = 0 tempInventory.newCount = 0 tempInventory.store_id = 1 //belongs to same store for now //Select a random store to belong to 0 through 2 since array starts at 0 let lo = 0; let hi = 2; let aRandomInt = Int.random(range:lo...hi) tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created. return tempInventory } func createStoreDummyData(number:Int) -> Store{ let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let tempStore = NSEntityDescription.insertNewObject(forEntityName: "Store", into: moc) as! Store tempStore.address = "100\(number) lane, Miami, FL" tempStore.email = "store\(number)@centraltire.com" tempStore.id = number as NSNumber? tempStore.lat = 1.00000007 tempStore.lng = 1.00000008 tempStore.name = "Store #\(number)" tempStore.phone = "123000000\(number)" return tempStore } // End DEMO Related Code override func viewDidLoad() { super.viewDidLoad() let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext print("InventoryController -> ViewDidLoad -> ... starting inits") // // Do any additional setup after loading the view, typically from a nib. // print("InventoryController -> ViewDidLoad -> ... starting inits") // //First check to see if we have entities already. There MUST be entities, even if its DEMO data. let inventoryFetchRequest = NSFetchRequest<Inventory>(entityName: "Inventory") //let storeFetchRequest = NSFetchRequest(entityName: "Store") do { let inventoryRecords = try moc.fetch(inventoryFetchRequest) //Maybe sort descriptor here? But how to organize into sectioned array? if(inventoryRecords.count<=0){ g_demoMode = true print("No entities found for inventory. Demo mode = True. Creating default entities & store...") //Reset the Stores g_storeList = [Store]() var store : Store //define variable as Store type for index in 1...3 { store = createStoreDummyData(number: index) g_storeList.append(store) } //save changes for inventory we added do { try moc.save() print("saved to entity") }catch{ fatalError("Failure to save context: \(error)") } var entity : Inventory //define variable as Inventory type for index in 1...52 { let indexFloat = Float(index/2)+1 let realIndex = Int(round(indexFloat)) entity = createInventoryDummyData(number: realIndex) g_inventoryItems.append(entity) } //Save the changes (UIApplication.shared.delegate as! AppDelegate).saveContext() print("finished creating entities") } }catch{ fatalError("bad things happened \(error)") } // //perform fetch we need to do. // do { // try fetchedResultsController.performFetch() // } catch { // print("An error occurred") // } print("InventoryController -> viewDidload -> ... finished inits!") } override func viewWillAppear(_ animated: Bool) { print("view appearing") //When the view appears its important that the table is updated. //Trigger Event on SearchBar in case returning from BarCode Scanner // self.searchBar:SearchBar textDidChange:recentlySearchedWord; //searchBar.performSelector(":textDidChange") //Perform another fetch again to get correct data~ do { //fetchedResultsController. //this will force setter code to run again. print("attempting fetch again, reset to use lazy init") fetchedResultsController = setFetchedResultsController() //sets it again so its correct. try fetchedResultsController.performFetch() } catch { print("An error occurred") } inventoryTable.reloadData()//this is important to update correctly for changes that might have been made } // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. print("inventoryItemControllerPrepareForSegueCalled") if segue.identifier == "inventoryInfoSegue" { let vc = segue.destination as! InventoryItemController vc.hidesBottomBarWhenPushed = true //hide the tab bar. This prevents crashing error from being on this page then syncing & returning. if let cell = sender as? InventoryTableViewCell{ vc.inventoryItem = cell.inventoryItem //sets the inventory item accordingly, passing its reference along. }else{ print("sender was something else") } } } // func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { // //This scrolls to correct section based on title of what was pressed. // return letterIndex.indexOf(title)! // } func sectionIndexTitles(for tableView: UITableView) -> [String]? { //This is smart and takes the first letter of known sections to create the Index Titles return self.fetchedResultsController.sectionIndexTitles } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sections = fetchedResultsController.sections { let currentSection = sections[section] return currentSection.numberOfObjects } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "InventoryTableCell", for: indexPath as IndexPath) as! InventoryTableViewCell print("IndexPath=") print(indexPath) let inventory : Inventory = fetchedResultsController.object(at: indexPath as IndexPath) cell.inventoryItem = inventory cell.drawCell() //uses passed inventoryItem to draw it's self accordingly. return cell } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if let sections = fetchedResultsController.sections { let currentSection = sections[section] return currentSection.name } return nil } func numberOfSections(in tableView: UITableView) -> Int { if let sections = fetchedResultsController.sections { return sections.count } return 0 } func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { //dispatch_async(dispatch_get_main_queue()) { //[unowned self] in print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason? let selectedCell = self.tableView(tableView, cellForRowAt: indexPath) as? InventoryTableViewCell self.performSegue(withIdentifier: "inventoryInfoSegue", sender: selectedCell) //} } @IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) { print("test of baritem") } @IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) { print("change store interface") } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { self.barcodeTextDidChange(searchText: searchText) } func barcodeTextDidChange(searchText: String){ print("text is changing") //Code to change NSFetchRequest Here~ & Reload Table m_searchText = searchText //sets the local variable to this class so the setFetchedResultsController() will update accordingly //Perform another fetch again to get correct data~ do { //fetchedResultsController. //this will force setter code to run again. print("attempting fetch again, reset to use lazy init") fetchedResultsController = setFetchedResultsController() //sets it again so its correct. try fetchedResultsController.performFetch() } catch { print("An error occurred") } inventoryTable.reloadData()//refreshes the data~ } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { print("ended by cancel") searchBar.text = "" m_searchText = "" //set the search text accordingly back to nothing. //Perform another fetch again to get correct data~ do { //fetchedResultsController. //this will force setter code to run again. print("attempting fetch again, reset to use lazy init") fetchedResultsController = setFetchedResultsController() //sets it again so its correct. try fetchedResultsController.performFetch() } catch { print("An error occurred") } inventoryTable.reloadData()//refreshes the data~ searchBar.resignFirstResponder() } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { print("ended by search") searchBar.resignFirstResponder() } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { print("ended by end editing") searchBar.resignFirstResponder() } func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { print("DidBeginEditing") //searchBar.keyboardType = UIKeyboardType.NamePhonePad } @IBAction func unwindBackToInventory(segue: UIStoryboardSegue) { print("unwind attempt") let barcode = (segue.source as? ScannerViewController)?.barcode searchBar.text = barcode! barcodeTextDidChange(searchText: searchBar.text!)//force it to re-run function manually. print("barcode="+barcode!) inventoryTable.reloadData()//reload the data to be safe. } } //Extention to INT to create random number in range. extension Int { static func random(range: ClosedRange<Int> ) -> Int { var offset = 0 if range.lowerBound < 0 // allow negative ranges { offset = abs(range.lowerBound) } let mini = UInt32(range.lowerBound + offset) let maxi = UInt32(range.upperBound + offset) return Int(mini + arc4random_uniform(maxi - mini)) - offset } }
globals.swift
import Foundation import CoreData //Array of Inventory & Store Core Data Managed Objects var g_inventoryItems = [Inventory]() var g_storeList = [Store]() var g_appSettings = [AppSettings]() var g_demoMode = false
Inventory+CoreDataProperties.swift
import Foundation import CoreData extension Inventory { @nonobjc public class func fetchRequest() -> NSFetchRequest<Inventory> { return NSFetchRequest<Inventory>(entityName: "Inventory"); } @NSManaged var addCount: NSNumber? @NSManaged var barcode: String? @NSManaged var barcodeReverse: String? @NSManaged var barcodeFourth: String? @NSManaged var currentCount: NSNumber? @NSManaged var id: NSNumber? @NSManaged var imageLargePath: String? @NSManaged var imageSmallPath: String? @NSManaged var name: String? @NSManaged var negativeCount: NSNumber? @NSManaged var newCount: NSNumber? @NSManaged var store_id: NSNumber? @NSManaged var store: Store? //This is used for A,B,C ordering... var lettersection: String { let characters = name!.characters.map { String($0) } return (characters.first?.uppercased())! } //This is used for 1,2,3 ordering... (using front of barcode and using barcodeReverse) var numbersection: String { let characters = barcode!.characters.map { String($0) } return (characters.first?.uppercased())! } //This is used for 0000000123 ordering...(uses back number of barcode) var numberendsection: String { let characters = barcodeReverse!.characters.map { String($0) } return (characters.first?.uppercased())! } //This is used for 0000000 -> 0123 ordering...(uses back 4th number of barcode) var numberfourthsection: String { let characters = barcodeFourth!.characters.map { String($0) } //print("characters") //print(characters) return (characters.first?.uppercased())! } }
Inventory.Swift
import Foundation import CoreData class Inventory: NSManagedObject { // Insert code here to add functionality to your managed object subclass }
Screenshots of Errors