Make a list of firebase users - swift

I am trying to make a list of all my users from Firebase with their username.
My problem is that I am trying to make an array of all the users.
import UIKit
import Firebase
var memberRef = Firebase(url: "\(BASE_URL)/users")
//var currentUser = DataService.dataService.USER_REF.authData.uid
var currentUsers: [String] = [String]()
class dataTableView: UITableViewController
{
override func viewDidLoad()
{
loadUsers()
}
func loadUsers()
{
// Create a listener for the delta additions to animate new items as they're added
memberRef.observeEventType(.ChildAdded, withBlock: { (snap: FDataSnapshot!) in
print(currentUsers)
// Add the new user to the local array
currentUsers.append(snap.value as! String)
// Get the index of the current row
let row = currentUsers.count - 1
// Create an NSIndexPath for the row
let indexPath = NSIndexPath(forRow: row, inSection: 0)
// Insert the row for the table with an animation
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Top)
})
}
But I get the error cant cast value of type NSDictionary to NSString.

The Firebase snapshot is a dictionary, not a string which is why you are receiving that error.
To access the elements of the snapshot:
if let name = snapshot.value["name"] as? String {
print(name)
}
you can also use
if let name = snapshot.value.objectForKey("name") as? String {

Related

Swift, Firebase - Get variable inside observe(.childAdded) closure [duplicate]

I'm trying to use Firebase to set the number of cells in my CollectionView. I tried to create a local variable and set that to the same value as the Firebase variable but when I try use it outside the function it doesn't work. I also tried setting it in ViewWillAppear but it didn't work.
I set the nav bar title to view the value. When it was set in the closure I got the correct value, when I wrote that outside the closure (after the firebase function), it gave a value of 0.
I'm using swift 3
override func viewWillAppear(_ animated: Bool) {
FIRDatabase.database().reference(withPath: "data").child("numCells").observeSingleEvent(of: .value, with: { (snapshot) in
if let snapInt = snapshot.value as? Int {
// self.navigationItem.title = String(snapInt)
self.numCells = snapInt
}
}) { (error) in
print(error.localizedDescription)
}
self.navigationItem.title = String(numCells)
}
...
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return numCells
}
Firebase is asyncronous and the data is only valid when it's returned from Firebase within the closure.
FIRDatabase.database().reference(withPath: "data").child("numCells")
.observeSingleEvent(of: .value, with: { snapshot in
if let snapInt = snapshot.value as? Int {
self.navigationItem.title = String(snapInt)
}
})
Expanding from that, suppose we want to populate an array to be used as a dataSource for a tableView.
class ViewController: UIViewController {
//defined tableView or collection or some type of list
var usersArray = [String]()
var ref: FIRDatabaseReference!
func loadUsers() {
let ref = FIRDatabase.database().reference()
let usersRef = ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot {
let userDict = child as! [String: AnyObject]
let name = userDict["name"] as! string
self.usersArray.append[name]
}
self.myTableView.reloadData()
})
}
print("This will print BEFORE the tableView is populated")
}
Note that we populate the array, which is a class var, from within the closure and once that array is populated, still within the closure, we refresh the tableView.
Note that the print function will happen before the tableView is populated as that code is running synchronously and code is faster than the internet so the closure will actually occur after the print statement.

Fatal Error: Index out of range - Swift 4 and Firebase

I am creating an iOS application using XCode 8 and Swift 4 that keeps track of events. I am using Firebase as my database tool for this app. I am running into an "index out of range" error in the code below. I am using a for loop to read the Event start and end times for each event in the Firebase Database. The code is able to return the event names, but not the times underneath. Why is this the case? Is there something wrong with my code, or is there something wrong with my database structure? The database structure used is pasted below the code here.
Code here:
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class Event_List_Controller: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var eventList = [String]()
var startTimeArray = [String]()
var endTimeArray = [String]()
var ref: DatabaseReference?
var handle:DatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
endTimeArray.removeAll()
tableView.delegate = self
tableView.dataSource = self
let emailfinal = UserDefaults.standard.string(forKey: "splicedEmailStandard")
ref = Database.database().reference()
handle = ref?.child(emailfinal!).child("Event Data").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.eventList.append(item)
self.tableView.reloadData()
}
})
// FOR LOOP THAT POPULATES THE START AND END TIMES
for individualEvents in eventList {
ref = Database.database().reference()
handle = ref?.child(emailfinal!).child("Event Data").child(individualEvents).child("Start Time").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.startTimeArray.append("\(item)")
}
})
handle = ref?.child(emailfinal!).child("Event Data").child(individualEvents).child("End Time").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.endTimeArray.append("\(item)")
}
})
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "customClassCell") as! CustomTableViewCell
cell.classLabel.text = eventList[indexPath.row]
// FATAL ERROR: INDEX OUT OF RANGE HERE.
cell.timeLabel.text = "\(startTimeArray[indexPath.row]) - \(endTimeArray[indexPath.row])"
return cell
}
}
Database Structure:
email#email (User)
-> User Information
----> (Information)
---> Event Information
------> Event Name
----------> Event Start Time
----------> Event End Time
----------> Event Location
email#email (User 2)
----> ...
It looks like you're calling reloadData() before you're actually getting the start and end times for the events, so the startTimeArray and endTimeArray will not have objects at the index of the event you just got from firebase.
Also consider that with Firebase you're setting up observing rather than getting all of your data at once. Perhaps some simple class like Event:
class Event {
var name: String
var startTime: String
var endTime: String
init(name: String) {
self.name = name
self.startTime = "" //or some other sensible default before loading
self.endTime = "" //or some other default
}
}
Then have an array of Event object that your tableview is using instead of the array of strings, and update the Event objects and refresh the table view whenever you get new data in from Firebase

UISearchBar textDidChange data from plist

I'd like to search through items of my plist. The plist consists of an array of dictionaries. Each key/value represents Strings/Ints, etc but that isn't important.
As you'll see in the tableViewController class below, I've currently got an array that I have typed. I know I need to make an array of objects/items from my plist but I can't work out how to reference objects from the plist in the view controller.
View controller.swift file:
import UIKit
class TableViewController: UITableViewController, UISearchResultsUpdating {
var array = ["Example 1", "Example 2", "Example 3"]
var filteredArray = [String]()
var searchController = UISearchController()
var resultsController = UITableViewController()
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: resultsController)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
}
//Added func to update search results
func updateSearchResults(for searchController: UISearchController) {
filteredArray = array.filter({ (array:String) -> Bool in
if array.contains(searchController.searchBar.text!) {
return true
} else {
return false
}
})
resultsController.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension TableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == resultsController.tableView {
return filteredArray.count
} else {
return array.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if tableView == resultsController.tableView {
cell.textLabel?.text = filteredArray[indexPath.row]
} else {
cell.textLabel?.text = array[indexPath.row]
}
return cell
}
}
I've tried solving this by creating an object class from a tutorial on plists. It uses the example of a periodic table of elements:
import UIKit
struct Element {
enum State: String {
case Solid, Liquid, Gas
}
let atomicNumber: Int
let atomicWeight: Float
let discoveryYear: String
let group: Int
let name: String
let period: Int
let radioactive: Bool
let state: State
let symbol: String
// Position in the table
let horizPos: Int
let vertPos: Int
}
extension Element {
enum ErrorType: Error {
case noPlistFile
case cannotReadFile
}
/// Load all the elements from the plist file
static func loadFromPlist() throws -> [Element] {
// First we need to find the plist
guard let file = Bundle.main.path(forResource: "Element", ofType: "plist") else {
throw ErrorType.noPlistFile
}
// Then we read it as an array of dict
guard let array = NSArray(contentsOfFile: file) as? [[String: AnyObject]] else {
throw ErrorType.cannotReadFile
}
// Initialize the array
var elements: [Element] = []
// For each dictionary
for dict in array {
// We implement the element
let element = Element.from(dict: dict)
// And add it to the array
elements.append(element)
}
// Return all elements
return elements
}
/// Create an element corresponding to the given dict
static func from(dict: [String: AnyObject]) -> Element {
let atomicNumber = dict["atomicNumber"] as! Int
let atomicWeight = Float(dict["atomicWeight"] as! String) ?? 0
let discoveryYear = dict["discoveryYear"] as! String
let group = dict["group"] as! Int
let name = dict["name"] as! String
let period = dict["period"] as! Int
let radioactive = dict["radioactive"] as! String == "True"
let state = State(rawValue: dict["state"] as! String)!
let symbol = dict["symbol"] as! String
let horizPos = dict["horizPos"] as! Int
let vertPos = dict["vertPos"] as! Int
return Element(atomicNumber: atomicNumber,
atomicWeight: atomicWeight,
discoveryYear: discoveryYear,
group: group,
name: name,
period: period,
radioactive: radioactive,
state: state,
symbol: symbol,
horizPos: horizPos,
vertPos: vertPos)
}
}
And in the viewController class, instead of having
var array = ["Example 1", "Example 2", "Example 3"]
I've tried variations of
var array = Element["name"]
and
var array = elements.name
But they obviously don't work because the reference to the plist is in the object class.
If anyone has any idea on how to solve this using swift 3/xcode 8 I would be very appreciative!!
I hope your question is still relevant. As I understand, you can't filter your array, right? If so, I recommend you to take a look at this and this tutorials. Both of them are representing a little bit different approaches to load filtered array, but it doesn't matter much, they work.
P.S. I don't recommend you to make a special tableView for searching, if you want to customize it hereafter, because you will have to do it programmaticly later.
It will be more efficiant to do like this:
If searchController.isActive {
// do some stuff
} else { // another stuff }
But it is just my opinion. I hope my question will help ;).
Thanks for the links Oleg. They were really good!!
It wasn't so much the filtering I had a problem with but actually parsing objects from my plist into a tableview. It took me a few days but I found an answer in case other people were also having the same problem. Keep in mind I'm reasonably new to this and so it might not be the best/perfect way of doing it but it works.
In viewDidLoad, I made a reference to the plist using the following code:
let path = Bundle.main.path(forResource: "Elements", ofType: "plist")
let dictArray = NSArray(contentsOfFile: path!)
I'm not sure of the relevance but I'm pretty sure this next bit is needed if the plist ever needed to be updated. (Also in viewDidLoad)
for elementItem in dictArray! {
let newElement : Element = Element(state:((elementItem as AnyObject).object(forKey: "state")) as! String,
atomicNumber:((elementItem as AnyObject).object(forKey: "atomicNumber")) as! Int,
atomicWeight:((elementItem as AnyObject).object(forKey: "atomicWeight")) as! Float,
discoveryYear:((elementItem as AnyObject).object(forKey: "discoveryYear")) as! String
etc. for each of the keys in the dictionary in the same order as in the Element object in the question. Then (also in viewDidLoad):
elementsArray.append(newElement)
Then its pretty easy, in cellForRow I just have to make a new variable that refers back to the object and I can call up each/any of the associated dictionary keys. E.g.:
let element : Element
cell.textLabel!.text = element.name
cell.detailTextLabel?.text = element.symbol
return cell
Like I said, I know its not perfect but it worked for me. I've heard its not best practice to put too much information in viewDidLoad, so someone else might be able to confirm or provide a better answer.

Getting a let value outside a function

Hello I'm new at swift programming and i want to get label value from loadData() to use use for my path (reference) to my database on dbRef!.child(place+"/placeLabel"). What I mean?! I read data that are on "Dio Con Dio" node. That happens because the value place is let place = "Dio Con Dio". So, my application doesn't load data from "Paradosiako - Panorama" node. I want to load everything from database and i thought that if i could make place value to change to the next node, it could read all the data.
import UIKit
import FirebaseDatabase
class PlacesTableViewController: UITableViewController {
//MARK: Properties
#IBOutlet weak var placesTableView: UITableView!
//database reference
var dbRef:FIRDatabaseReference?
var places = [Places]()
private var loadedLabels = [String: String]()
private var loadedRatings = [String: Int]()
//handler
var handle:FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
dbRef = FIRDatabase.database().reference()
// Loads data to cell.
loadData()
}
private func loadData() {
let place = "Dio Con Dio"
dbRef!.child(place+"/placeLabel").observe(.childAdded, with: {
(snapshot) in
let label = snapshot.value as! String
self.updatePlace(snapshot.key, label: label)
})
dbRef!.child(place+"/rating").observe(.childAdded, with: {
(snapshot) in
let rating = snapshot.value as! Int
self.updatePlace(snapshot.key, rating: rating)
})
}
private func updatePlace(_ key: String, label: String? = nil, rating: Int? = nil) {
if let label = label {
loadedLabels[key] = label
}
if let rating = rating {
loadedRatings[key] = rating
}
guard let label = loadedLabels[key], let rating = loadedRatings[key] else {
return
}
if let place = Places(name: label, rating: rating) {
places.append(place)
placesTableView.reloadData()
}
}
}
(This question is a follow up from this one.)
It's difficult to explain this in a comment, so I'll address your current issue here, but Paulo answered your question, I believe. You could even open a new question on this issue.
Here's the code you're having an issue with:
for (key, rating) in ratings.value as! [String: Int] {
self.updatePlace(key, rating: rating)
}
There's nothing wrong with this code, as long as ratings.value is filled with string: integer pairs. Your error occurs because somehow a there is a key missing. (the runtime found a NSNull, so it couldn't convert the data to a dictionary)
You need to set a breakpoint where you are constructing the array in:
placeSnapshot.childSnapshot(forPath: "rating")
(which is called ratings here/locally). If is ok to ignore entries with no key, then skip them and don't append them to your array. But it's more likely you have an error and need to find out why you're adding a null value...
Does that make more sense? Please accept Paulo's answer if he solved an issue, and open a new question with more information about childSnapshot (the code), and someone will be sure help you. (I know I'll try.)
For your new database schema, try this instead:
private func loadData() {
dbRef!.observe(.childAdded) {
(placeSnapshot) in
print("Adding place \(placeSnapshot.key)...")
// Load each label on the "placeLabel" list.
let labels = placeSnapshot.childSnapshot(forPath: "placeLabel")
for (key, label) in labels.value as! [String: String] {
self.updatePlace(key, label: label)
}
// Load each rating in the "rating" list.
let ratings = placeSnapshot.childSnapshot(forPath: "rating")
for (key, rating) in ratings.value as! [String: Int] {
self.updatePlace(key, rating: rating)
}
}
}
The updatePlace function simply groups the label and rating properties, of a given place, that were separately loaded by loadData.
Places removal. Of course, the above code won't handle place removal. To do so, you will need to listen for the childRemoved event as well.

FetchedResultsController Swift 3 API Misuse: Attempt to serialize store access on non-owning coordinator

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
I have reviewed your all comments and contents posted here.
You have not shared one file here, but the problem is occurring you are creating invalid managed objects in the context.
And then whenever you call viewWillAppear() function in InventoryViewController, it saves the context.
Finally, it synced empty records into your database.
During parsing those invalid objects, it tried to parse nil value, so crashed.
Please never set default value for managed objects you are defining as properties.
I hope this will clarify your issue.
I was running into similar issue and i moved to the new CoreData api introduced in ios10.
This uses the NSPersistentContainer class to create the stack and create associated contexts.
This eliminates the need to manually call save or order the creation of fetch results controller.
Good blog post to read: https://useyourloaf.com/blog/easier-core-data-setup-with-persistent-containers/
My setup is a follows
create a store NSPersistentContainer
let persistentContainer = NSPersistentContainer(name: "ModelFileName");
configure settings
let url = NSPersistentContainer.defaultDirectoryURL()
let path = url.appendingPathComponent(persistentContainer.name);
description.shouldAddStoreAsynchronously = true; //write to disk should happen on background thread
self.persistentContainer.persistentStoreDescriptions = [description];
load the store
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error), \(error.localizedDescription)")
}
//configure context for main view to automatically merge changes
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true;
});
in the view controller you can access the view context by calling
persistentContainer.viewContext
if you need to make changes you can call
persistentContainer.performBackgroundTask({ (context) in ... });
or you can get a background context
let context = persistentContainer.newBackgroundContext()
context.perform({ ... })
In case this helps anyone else who gets the "API Misuse: Attempt to serialize store access on non-owning coordinator" error - I was getting the error because I accessed an object in a singleton that had not been destroyed and was still using the old NSManagedObjectContext after I reset the NSPersistentStore and NSManagedObjectContext.