Removing Firestore SnapshotListener - swift

I am currently working on a valet app. I have two table views one for the Location of the worker, and then another that when you click on a location the cars that are under that location come up in the new tableview.
My current problem is that when I click on a new location the old snapshot listener is still on and if an update happens under that location the second table view changes back to the original location.
I tried to do what they say by removing listener but then it just doesn't work at all.
This is my code below
// func to select the individual row
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch tableView {
case SelectLocationTableView:
do {
if (allowed == false){ // postin data in table already but not reloading the page yet
Authenticate(title: "hi", message: "hi")
Location = postData[indexPath.row]
oldLocation=Location
}
}
do {
if (allowed == true) {
allowed = false
Location = postData[indexPath.row]
CarsRequestedTableView.reloadData()
DisplayLocations()
}
}
case CarsRequestedTableView:
CarSelected = postCars[indexPath.row]
WorkWithCar()
default:
print(Error.self)
}
}
// function to pull the location based on the cell you click on
func DisplayLocations (){
let listener = db.collection("Requested").document(Location).collection("Requested").addSnapshotListener { querySnapshot, error in
guard (querySnapshot?.documents) != nil else {
print("Error fetching documents: \(error!)")
return
}
self.postCars.removeAll()
for document in querySnapshot!.documents {
let post = document.documentID
self.postCars.append(post)
self.CarsRequestedTableView.reloadData()
self.DisplayAlert(title: "NEW REQUEST!", message: "CHECK FOR NEW REQUEST!")
}
}
}

You need to keep the listener registration in a (private) instance variable in your table view controller, and call its remove() method when you're ready to stop listening. The Firebase iOS Quickstart repo has a couple of classes that show how to do this, here is an example I took from RestaurantsTableViewController:
class RestaurantsTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// ...
// the data that's displayed in the table view
private var restaurants: [Restaurant] = []
private var documents: [DocumentSnapshot] = []
// this method removes any existing listener and sets up a new query
fileprivate var query: Query? {
didSet {
if let listener = listener {
listener.remove()
observeQuery()
}
}
}
// this is where you keep the listener registration
private var listener: ListenerRegistration?
// set up the new snapshot listener and map documents to data
fileprivate func observeQuery() {
guard let query = query else { return }
stopObserving()
// Display data from Firestore, part one
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Restaurant in
let maybeModel: Restaurant?
do {
maybeModel = try document.data(as: Restaurant.self)
} catch {
fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data()): \(error)")
}
if let model = maybeModel {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Missing document of type \(Restaurant.self) at \(document.reference.path)")
}
}
self.restaurants = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
}
}
// this is where you stop the listener
fileprivate func stopObserving() {
listener?.remove()
}
/// ...
}

Related

Swift - How to sort documents fetched from Firestore Database by date?

I have a tableViewController that gets loaded with documents fetched from Firestore Database. I would like to get them sorted by date and not ID, but I don't know where exactly to use sortby(). The documents already have a variable with the date, I am just not sure where I add the .sort(by: "date").
I already checked around, but most people have a very different code to populate their tableviews, and mine looks completely different.
Since I am new with Swift it took a lot of effort to get the tableViewController to work properly (mainly through online tutorials), but I don't fully understand it. This is the code:
This is an extension
class EncontradoService {
let database = Firestore.firestore()
func get(collectionID: String, handler: #escaping ([Encontrado]) -> Void) {
database.collection("EncontradosFinal")
.addSnapshotListener { querySnapshot, err in
if let error = err {
print(error)
handler([])
} else {
handler(Encontrado.build(from: querySnapshot?.documents ?? []))
}
}
}
}
And this is in the tableViewController
private var service: EncontradoService?
private var allencontrados = [Encontrado]() {
didSet {
DispatchQueue.main.async {
self.encontrados = self.allencontrados
}
}
}
var encontrados = [Encontrado]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad()
{
super.viewDidLoad()
loadData()
}
func loadData() {
service = EncontradoService()
service?.get(collectionID: "EncontradosFinal") { encontrados in
self.allencontrados = encontrados
}
}
Thanks!

Accessing Firestore data outside of Function [duplicate]

This question already has an answer here:
Assign value of a Firestore document to a variable
(1 answer)
Closed 3 years ago.
I have a FireStore function in my FirestoreService file as below;
func retrieveDiscounts() -> [Discount] {
var discounts = [Discount]()
reference(to: .discounts).getDocuments { (snapshots, error) in
if error != nil {
print(error as Any)
return
} else {
guard let snapshot = snapshots else { return }
discounts = snapshot.documents.compactMap({Discount(dictionary: $0.data())})
}
}
return discounts
}
how do I get returned values to populate my private var discounts = [Discount]() variable in my viewController
Many thanks as always...
Your functions will get your UI to freeze until its operation is complete. The function which may take long duration to complete should be done asyncronous using escaping closures. The function should be like below :
func retrieveDiscounts(success: #escaping([Discount]) -> ()) {
var discounts = [Discount]()
reference(to: .discounts).getDocuments { (snapshots, error) in
if error != nil {
print(error as Any)
success([])
return
} else {
guard let snapshot = snapshots else { return }
discounts = snapshot.documents.compactMap({Discount(dictionary: $0.data())})
success(discounts)
}
}
}
Note: The data returns empty if error. Please handle error case if you need.
We first need an instance of FirestoreService class. Then the instance should call the retrieveDiscounts() function and populate it to our instance i.e. discounts.
Code:
class ViewController: UIViewController {
private var discounts = [Discount]() {
didSet {
self.tableView.reloadData()
}
}
func viewDidLoad() {
super.viewDidLoad()
FirestoreService().retrieveDiscounts { discounts in
self.discounts = discounts
}
}
}

Wait multiple observable requests to finish using RXSwift

I have a list of observables that are requests for google distance and duration info from an specific point. I'm trying to load my screen only when all this information is fetched, but my subscribe on next for those observables are never called (the line "observer.onNext(viewModel)" is called and has the information already fetched, only the subscribe(onNext) is not being called). How can I wait til those observables complete?
func stationInfoObservable(userLocation: CLLocationCoordinate2D, stations: [Station]) -> [Observable<GasStationTableCellViewModel>] {
var observables: [Observable<GasStationTableCellViewModel>] = []
for station in stations {
observables.append(Observable.create({ observer in
guard let toCoordinate = station.coordinate() else { return Disposables.create() }
self.mapDirections.routes(from: userLocation.asPlace(), to: toCoordinate.asPlace()) { routes, error in
if let error = error {
logger.error(error)
} else {
guard let leg = routes.first?.legs?.first else {
return
}
guard let distance = leg.distance?.text, let duration = leg.duration?.text else { return }
station.distanceInKMFromUserLocation = distance
station.distanceInMinutesFromUserLocation = duration
let viewModel = GasStationTableCellViewModel(station: station)
observer.onNext(viewModel)
observer.onCompleted()
}
}
return Disposables.create()
}))
}
return observables
}
I'm trying to subscribe this way (EDIT: I'm now trying to use zip, but the the drive / subscribe continues not being called):
Observable.zip(observables)
.asDriver(onErrorJustReturn: [])
.drive(onNext: { test in
print(test)
}, onCompleted: {
print("aa")
}).disposed(by: DisposeBag())
Based on your subscription code, it looks like you're not retaining the DisposeBag. You must retain this object because when it gets deallocated, all disposables it owns get immediately disposed. Try making it a property and use the property:
class MyClass {
let disposeBag = DisposeBag()
func setupSubscription() {
Observable.zip(observables)
.asDriver(onErrorJustReturn: [])
.drive(onNext: { test in
print(test)
}, onCompleted: {
print("aa")
}).disposed(by: disposeBag)
}
}

When to detach firebase listeners in tableView cell?

In chat app, in order to keep track of each chat's last message and unread messages for each user participating in chat, when the tableView cell is dequeued I am attaching a .childChanged listener on the cell. When the listener is fired, I update the chat label.text on each row for the corresponding chat.
When should I remove these listeners or what is the best practice to update the chat in the cell in my case?
What is the flow of the program?
1. Download current user
2. Download current user chatIDs
3. Download chat for each chatID
4. Populate tableView with chats
5. In each cell observe childChanged at
chats/chat.chatUID/currentUserUID/.observe(.childChanged)
6. If "unreadMessagesCount" was changed, update it on the cell
class ChatTableViewCell: UITableViewCell {
#IBOutlet weak var lastMessageLabel: UILabel!
var chat: Chat! {
didSet{
self.updateUI()
}
}
func updateUI() {
self.chat.observeChildChanged(chat: self.chat, currentUserUID:user.userUID) { (lastMessage, unreadMessagesCount) in
if !lastMessage.isEmpty{
self.lastMessageLabel.text = lastMessage
}
if unreadMessagesCount > 0 {
self.lastMessageLabel.font = UIFont.boldSystemFont(ofSize: 16.0)
self.chatUnreadMessagesCount.text = "\(unreadMessagesCount)"
} else {
self.lastMessageLabel.font = UIFont.systemFont(ofSize: 15.0)
self.chatUnreadMessagesCount.text = ""
}
}
}
}
class MessagesViewController: UITableViewController {
override func viewDidLoad() {
//observe ~/users/uid
DDatabaseRReference.users(uid: uid).reference().observeSingleEvent(of: .value, with: { (snapshot) in
guard snapshot.exists() else {return}
if let userDict = snapshot.value as? [String : Any] {
self.currentUser = UserModel(dictionary: userDict)
self.userWasDownloaded = true //this will trigger the setter and start downloading chatId's of current user
}
})
}
var userWasDownloaded: Bool {
get {
return true
}
set {
self.fetchChatsIdsOf(currentUser: self.currentUser)
self.tableView.reloadData()
}
}
func fetchChatsIdsOf(currentUser: UserModel) {
//get chatIds of currentUser from ~/users/currentUser.userUID/chatIds
DDatabaseRReference.users(uid: currentUser.userUID).reference().child("chatIds").observe(.childAdded, with: { (snapshot) in
let chatUID = snapshot.key
if !self.chatIdsDownloaded.contains(chatUID) {
self.chatIdsDownloaded.append(chatUID)
}
})
}
//after chatIdsDownloaded is set,
//download the new chat for the last chat appended to chatIdsDownloaded array
var chatIdsDownloaded = [String]() {
didSet {
guard let chatID = chatIdsDownloaded.last else {return}
self.downloadNewChat(chatID: chatID)
}
}
func downloadNewChat(chatID: String) {
DDatabaseRReference.chats.reference().child(chatID).observeSingleEvent(of: .value, with: { (snapshot) in
......
self.currentUserChats.insert(chatChecked, at: 0)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatTableViewCell", for: indexPath) as! ChatTableViewCell
cell.chat = currentUserChats[indexPath.row]
return cell
}
}
chats // <- all chats in the app for all users in the app
-LOMVtcjOEOu2p1apMKV
chatUID: "-LOMVtcjOEOu2p1apMKV"
isGroupChat: true
lastMessage: "Katherine Gregory has joined the group"
lastUpdate: 1539761870.2237191
+users
IN4pgCS5NqQZZLpdmoz1KeDiFqj2
fcmToken: ""
firstName: "Alex"
userUID: "IN4pgCS5NqQZZLpdmoz1KeDiFqj2"
unreadMessagesCount: 5
users // <- all users in the app
IN4pgCS5NqQZZLpdmoz1KeDiFqj2
+chatIds
-LOMVtcjOEOu2p1apMKV: true
- name: ""
- email: ""
...etc
You can check if you added 2 or more observers for cell.
Add breakpoint or print() here:
self.chat.observeChildChanged(chat: self.chat, currentUserUID: user.userUID) { (lastMessage, unreadMessagesCount) in {
//breakpoint or print("observeChildChanged")
...
}
Please, reuse your cell.
Send new message.
If you have 2 or more messages it means that you have not set only one observer.
Perhaps this approach is not perfect, but it can help you (remove old observers before add new):
var chat: Chat! {
didSet {
self.removeOldObserver()
self.updateUI()
}
}
func removeOldObserver() {
...
}
As Jay suggested, I am attaching .childChanged observer on each chat newly downloaded.
However, if I go to firebase console and update the value of a child at ref path, childChanged observer is not always triggered. Sometimes, it works, sometimes it does not What could be the problem?
I am using break points on all lines and none of them is hit when value is changed in database.
example: name:"Alex"
change with: name: "John"
UPDATE
This answer is correct, I had forgotten to delete my previous implementation in which was detaching the listeners in prepareForReuse()
var currentUserChats = [Chat]() {
didSet(newValue){
attachChildChangedObserverOn(chat: newValue)
}
}
var observersArray = [String: UInt]() // chatUID:handle
func attachChildChangedObserverOn(chat: Chat) {
var handle: UInt = 0
let ref = DDatabaseRReference.chats.reference().child(chat.chatUID).child("users").child(currentUser.userUID)
handle = ref.observe(.childChanged, with: {[weak self] (snapshot) in
self?.observersArray[chat.chatUID] = handle
print("snapshot.value is \(snapshot.value) and snapKey is \(snapshot.key)")
guard snapshot.exists() else {return}
let chatChanged = chat
var lastMessage = ""
var unreadMessagesCount = 0
var lastUpdate = 0.0
switch snapshot.key {
//case....
}
})
}

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.