Tableview Updating wrong cell - swift

I made a like button problem is when the like button is tapped the values update only the first tableview cell even if I clicked the second cell it will update the values of likes in the first tableview cell. The values update properly on firestore for each individual cell but its being loaded wrong
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
motivationThoughts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyThoughtCELL", for: indexPath) as? dailyMotivationTableViewCell
cell!.generateCellsforDailymotivation(_MotivationdataMODEL: self.motivationThoughts[indexPath.row], objectID: self.motivationThoughts[indexPath.row].motiveID)
//cell!.postID = self.motivationThoughts[indexPath.row].motiveID //this works and updates the value
//cell!.generateLikeCell(objectID: self.motivationThoughts[indexPath.row].motiveID)
return cell!
}
func loaddailymotivation() {
FirebaseReferece(.MotivationDAILY).addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) { // this line means if the chage that happened in the document was equal to added something
let data = diff.document.data()
print("we have\(snapshot.documents.count) documents in this array")
let dailyMotivationID = data["objectID"] as! String
let dailymotivationTitle = data["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = data["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = data["Number of likes in daily motivation post"]as! Int
let MdataModel = motivationDailyModel(RealMotivationID: dailyMotivationID, RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes)
self.motivationThoughts.append(MdataModel)
}
/*
I thinkyou need a method that identifies the item that needs to be modified in the array, and replace/modify it. If you add a new object, you have one more element in your table*/
if (diff.type == .modified) {
print("Modified data: \(diff.document.data())")
let newdata = diff.document.data()
let objectID = newdata["objectID"] as! String // we get the object id of the uodated item
//self.GrabThatDamnstring(grabIt: objectID)
//self.incrementLikes(NewobjectID: objectID) //write a function to grab the string from the certain post and then send it to dailymotivationviewcell into the function increment likes so so it can update the specific objectID
guard let dailymotivationIndex = self.motivationThoughts.firstIndex(where: {_ in objectID == objectID}) else { return }
var dailymotivation = self.motivationThoughts[dailymotivationIndex]
let dailyMotivationNumberOfLikes = newdata["Number of likes in daily motivation post"] as! Int
dailymotivation.motivationNumberOfLikes = dailyMotivationNumberOfLikes
self.motivationThoughts[dailymotivationIndex] = dailymotivation
// here you will receive if any change happens in your data add it to your array as you want
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
}
this is the uitableview cellcode
import UIKit
import FBSDKLoginKit
import Firebase
import JGProgressHUD
class dailyMotivationTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//numberOfLikesGenerator()
self.holdView.layer.cornerRadius = 19
self.likedbuttonFIlled.isHidden = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBOutlet weak var holdView: UIView!
#IBOutlet weak var likedbuttonFIlled: UIImageView!
#IBOutlet weak var likeSavedButton: UIButton!
#IBOutlet weak var DMtextLabel: UILabel!
#IBOutlet weak var DMtitleLabel: UILabel!
#IBOutlet weak var numberOfLikesLabel: UILabel!
//MARK: VARS/LETS
var postID : String!
var MotivationData : motivationDailyModel!
let hud = JGProgressHUD(style: .light)
//MARK: IBACTIONS
#IBAction func likeButtonTapped(_ sender: Any) {
if (Auth.auth().currentUser != nil || AccessToken.current != nil) {
changeLikeButtonMode()
//update like number and update like number on firebase
}
else{
hud.textLabel.text = "Please Login to Continue!"
hud.show(in: self.contentView)
hud.dismiss(afterDelay: 3.0)
hud.indicatorView = JGProgressHUDErrorIndicatorView()
//no user login so pull up login view
}
//MARK: TODO when this button is tapped ALSO WANT TO STORE THIS SNAPSHOT INTO AN ARRAY THAT WE WILL SHOW IN OUR SAVED VIEW CONTROLLEr
}
//call this function from motivation daily viewcontroller and it will also have the item that was tapped
func incrementLikes(){
//MARK: WE NEED TO FIND OUT HOW WE GET THE UUID FOR THE CERTAIN POS
FirebaseReferece(.MotivationDAILY).document(postID).updateData(["Number of likes in daily motivation post":FieldValue.increment(Int64(1))]) { (error) in
if error != nil {
print(error!.localizedDescription)
} else {
print("successfully incremented data!")
}
}
}
func changeLikeButtonMode(){
// so if likedbutton is tapped and the heart isnt red that means that the tag is = 0 so its gnna show the red heard and then also change the tag to 1 but when it is tapped again its going to change the tag to 0 and removed the red heart
if likeSavedButton.tag == 0 //means its empty
{
incrementLikes()
self.likedbuttonFIlled.isHidden = false
likeSavedButton.tag = 1
}else {
self.likedbuttonFIlled.isHidden = true
likeSavedButton.tag = 0
}
}
//MARK: FUNCTIONS
func generateCellsforDailymotivation(_MotivationdataMODEL : motivationDailyModel,objectID : String!) {
DMtextLabel.text = _MotivationdataMODEL.motivationDailyScripture
DMtitleLabel.text = _MotivationdataMODEL.motivationTitle
numberOfLikesLabel.text = "\(String(_MotivationdataMODEL.motivationNumberOfLikes))"
postID = objectID
}
}

The condition in your guard statement always evaluates to true
guard let dailymotivationIndex = self.motivationThoughts.firstIndex(where: {_ in objectID == objectID}) else { return }
Try this instead
guard let dailymotivationIndex = self.motivationThoughts.firstIndex(where: { $0.RealMotivationID == objectID}) else { return }

Related

use func to delete a specific item from core data binary data

I am trying to delete binary data from core data. I am using a var int 'place' to determine what specific item I am trying to delete I am getting a runtime error under helpImage.shareInstance.deleteInfo(info: place) which is below.
Cannot convert value of type 'Int' to expected argument type 'Info'
What can I do to delete the 1st item saved in a core data binary attribute?
import UIKit;import CoreData
class ViewController: UIViewController {
var place = 0
override func viewDidLoad() {
super.viewDidLoad()
let gwen = UIImage(named: "unnamed.jpg")
if let imageData = gwen.self?.pngData() {
helpImage.shareInstance.saveImage(data: imageData)
}
let alz = UIImage(named: "alba.jpeg")
if let imageData = alz.self?.pngData() {
helpImage.shareInstance.saveImage(data: imageData)
}
}
#objc func deleteM(){
helpImage.shareInstance.deleteInfo(info: place)
}
}
class helpImage: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpImage()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveImage(data: Data) {
let imageInstance = Info(context: context)
imageInstance.img = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func deleteInfo(info: Info) {
do {
try context.delete(info)
} catch {
print(error.localizedDescription)
}
}
}
I'm also a newbie in swift and learning so if anyone has feedback, I'm more than happy to implement.
here is stroryBoard: on "Save" button click, we will save images in CoreData and on "Show" button click, we will display a tableView with our images fetched from CoreData
here is coreData: don't forget to check Manual/None in Codegen in coreData Class
then manually add coreData NSManagedObject SubClass files (there will be two files).
To generate the class and properties files initially:
source: https://developer.apple.com/documentation/coredata/modeling_data/generating_code
From the Xcode menu bar, choose Editor > Create NSManagedObject Subclass.
Select your data model, then the appropriate entity, and choose where to save the files. Xcode places both a class and a properties file into your project.
// this is how your "Picture+CoreDataClass.swift" looks like
import Foundation
import CoreData
#objc(Picture)
public class Picture: NSManagedObject {
}
// this how your "Picture+CoreDataProperties.swift" looks like
import Foundation
import CoreData
extension Picture {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Picture> {
return NSFetchRequest<Picture>(entityName: "Picture")
}
#NSManaged public var pic: String?
#NSManaged public var id: Int64
}
extension Picture : Identifiable {
}
I have used below extension to get currentTimeStamp as we will need unique ID for each of our coreData object, we will pass currentTimeStamp as ID to be unique.
// MARK: - Get the Current Local Time and Date Timestamp
source: https://stackoverflow.com/questions/46376823/ios-swift-get-the-current-local-time-and-date-timestamp
extension Date {
static var currentTimeStamp: Int64{
return Int64(Date().timeIntervalSince1970 * 1000)
}
}
I have made CRUD functions in DataBaseHelper.swift file
// this "DataBaseHelper" Class
import Foundation
import UIKit
import CoreData
class DataBaseHelper {
// MARK: - Get Context
class func getContext() -> NSManagedObjectContext{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = DataBaseHelper()
let context = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Picture")
// MARK: - Save Images
func saveImage(picString: String, id: Int64 = Date.currentTimeStamp) {
// here I have passed id = Date.currentTimeStamp, as we need unique ID for each of our object in coreData and Date.currentTimeStamp will always give unique integer. we will use this ID to update and delete particular object.
let entity = NSEntityDescription.entity(forEntityName: "Picture", in: context)!
let image = NSManagedObject(entity: entity, insertInto: context)
image.setValue(picString, forKey: "pic") // key should be same as the attributes taken in coreData Table.
image.setValue(id, forKey: "id") // key should be same as the attributes taken in coreData Table.
do {
try context.save()
print("Images saved in coreData")
print("imageString: \(picString), id: \(id)") // this will print your entered (saved) object in coreData
} catch let error {
print("Could not save images: \(error.localizedDescription)")
}
}
// MARK: - fetch Images
func fetchImages() -> [Picture] {
var arrImages = [Picture]()
fetchRequest.returnsObjectsAsFaults = false
do {
arrImages = try context.fetch(fetchRequest) as? [Picture] ?? [Picture]()
print("Images while fetching from coreData: \(arrImages)") // this will print all objects saved in coreData in an array form.
} catch let error {
print("Could not fetch images: \(error.localizedDescription)")
}
return arrImages
}
// MARK: - fetch Images by ID
func fetchImagesByID(id: Int64) -> Picture {
fetchRequest.returnsObjectsAsFaults = false
let predicate = NSPredicate(format: "id == \(id)")
fetchRequest.predicate = predicate
let result = try? context.fetch(fetchRequest)
return result?.first as? Picture ?? Picture()
}
// MARK: - Update Image
func updateImage(object: Picture) {
let image = fetchImagesByID(id: object.id) // we will first fetch object by its ID then update it.
image.pic = object.pic
do {
try context.save()
print("Image updated in CoreData")
print("after updating Picture --> \(object)")
} catch let error {
print("Could not update Picture: \(error.localizedDescription)")
}
}
// MARK: - Delete Image
func deleteImage(id: Int64) {
let image = fetchImagesByID(id: id) // we will first fetch object by its ID then delete it.
context.delete(image)
do {
try context.save()
print("Image deleted from CoreData")
} catch let error {
print("Could not delete Image --> \(error.localizedDescription)")
}
}
}
here is our ViewController:
I have added 4 images in Assets folder.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var btnSaveImages: UIButton!
#IBOutlet weak var tableViewPicture: UITableView!
#IBOutlet weak var btnShowImages: UIButton!
var resultImages = [Picture]() // this an an instance of our model class (coreData)
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "CoreData Demo"
setUpTableView()
}
// MARK: - Save Images
func saveImagesInCoreData() {
// I have added 4 images in Assets folder, here we will save 3 images in CoreData
let image1 = "flower1"
let image2 = "flower2"
let image3 = "flower3"
DataBaseHelper.shareInstance.saveImage(picString: image1)
DataBaseHelper.shareInstance.saveImage(picString: image2)
DataBaseHelper.shareInstance.saveImage(picString: image3)
}
// MARK: - Fetch Images
func fetchImagesFromCoreData() {
resultImages = DataBaseHelper.shareInstance.fetchImages() // this will give fetched images
}
// MARK: - Set Up TableView
func setUpTableView () {
tableViewPicture.delegate = self
tableViewPicture.dataSource = self
}
// MARK: - Button Save Images Event
#IBAction func btnSaveImages_Event(_ sender: UIButton) {
saveImagesInCoreData() // save images in CoreData
}
// MARK: - Button Show Images Event
#IBAction func btnShowImages_Event(_ sender: UIButton) {
fetchImagesFromCoreData() // fetch Images from CoreData
self.tableViewPicture.reloadData() // reload tableView
}
}
// MARK: - Extesnion TableViewDelegate and TableViewDataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return resultImages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell") ?? UITableViewCell()
cell.imageView?.image = UIImage(named: resultImages[indexPath.row].pic ?? "")
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .default, title: "Edit") { (action, indexPath) in
print("Action Edit")
// here we will edit image of selected row in our tableView, we will update selected row with new image that is "flower4".
let image4 = "flower4"
self.resultImages[indexPath.row].pic = image4
DataBaseHelper.shareInstance.updateImage(object: self.resultImages[indexPath.row]) // update image of selected row in tableView
self.tableViewPicture.reloadData()
}
editAction.backgroundColor = .lightGray
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
print("Action Delete")
// here we will delete an object of selected row in our tableView.
DataBaseHelper.shareInstance.deleteImage(id: self.resultImages[indexPath.row].id) // delete object of selected row in tableView
self.resultImages.remove(at: indexPath.row) // remove from resultImages array
self.tableViewPicture.reloadData()
}
return [deleteAction, editAction]
}
}
if you have doubt feel free to ask !

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....
}
})
}

UITableView with Segmented Controller not displaying data immediately - Swift

I have a Tab Bar Controller, and one of the two views from it is a TableViewController. The TableViewController has a SegmentedControl bar on the top of the table to switch between two datasets pulled from Firebase.
When I run the app, the table doesn't show any data straight away, only when I switch to the other segment of the SegmentControl bar. But, when I switch back, the data for the first segments loads.
I put in a breakpoint to see what was happening, and it was skipping over the code I wrote to pull the data from Firebase, so the arrays were empty upon initial loading, hence the lack of data. Yet, when I switch segments to the other option, the data appears.
I'm also trying to sort the data within the arrays, and this doesn't do anything at all because it runs when the arrays come back empty, so there is nothing to sort.
My code is:
class LeaderboardViewController: UITableViewController
{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet var leaderboardTable: UITableView!
var countyRef = Database.database().reference().child("countyleaderboard")
var schoolRef = Database.database().reference().child("schoolleaderboard")
var refHandle: UInt!
var countyList = [County]()
var schoolList = [School]()
override func viewDidLoad()
{
super.viewDidLoad()
fetchData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
var sectionCount = 0
switch(segmentedControl.selectedSegmentIndex)
{
case 0:
sectionCount = countyList.count
break
case 1:
sectionCount = schoolList.count
break
default:
break
}
return sectionCount
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let boardCell = tableView.dequeueReusableCell(withIdentifier: "boardCell", for: indexPath)
switch(segmentedControl.selectedSegmentIndex)
{
case 0:
boardCell.textLabel!.text = "" + countyList[indexPath.row].name! + ": \(countyList[indexPath.row].score!)"
break
case 1:
boardCell.textLabel!.text = "" + schoolList[indexPath.row].name! + ": \(schoolList[indexPath.row].score!)"
break
default:
break
}
return boardCell
}
func fetchData()
{
schoolRef.observe(.childAdded, with:
{ snapshot in
if let dictionary = snapshot.value as? [String: AnyObject]
{
let school = School()
school.name = dictionary["name"] as! String
school.score = dictionary["score"] as! Float
self.schoolList.append(school)
}
}, withCancel: nil)
self.schoolList.sort(by: { $0.score > $1.score })
countyRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children
{
let snap = child as! DataSnapshot
let county = County()
county.name = snap.key as String
county.score = snap.value as! Float
self.countyList.append(county)
}
})
self.countyList.sort(by: { $0.score > $1.score })
DispatchQueue.main.async
{
self.leaderboardTable.reloadData()
}
}
#IBAction func segmentedControlChanged(_ sender: Any)
{
DispatchQueue.main.async
{
self.leaderboardTable.reloadData()
}
}
}
My questions are:
Why does the data not load straight away?
Where are the arrays coming from if they are not being populated with data on the first run? And why are they not being sorted if that code is directly below the code that populates them?
The dispatch block should be after the for loop and before the }).
It should be within the observe/observeSingleEvent. Also your sort code needs to be inside the same block. Like below:
countyRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children
{
let snap = child as! DataSnapshot
let county = County()
county.name = snap.key as String
county.score = snap.value as! Float
self.countyList.append(county)
}
self.countyList.sort(by: { $0.score > $1.score })
DispatchQueue.main.async
{
self.leaderboardTable.reloadData()
}
})

issues loading items from firebase to a pickerview

I have successfully created a QuestionModel class which retrieves items from firebase such, as a Question, string of answers and a correct question. I am however now having difficulty in terms of getting these items to go into a picker view from another class. The class called QuestionsViewController is where I am having an issue in terms of using the questions class to retrieve data from. The QuestionModel class contains retrieving data from firebase. I am having bad execution errors throughout the code in the QuestionsViewController class. This mainly occurs when trying to set the itemlabel text before the pickverview and code for the pickerview functions.
import Foundation
import Firebase
import FirebaseDatabase
import FirebaseAuth
import CoreData
class QuestionList
{
//properties
public static var Username: String = ""
private static var quiz = [Question]()
static func getDummyQuestions()->[Question]
{
//create some dummy data for the model
var ref: FIRDatabaseReference!
var refHandle: UInt!
ref = FIRDatabase.database().reference() //reference
refHandle = ref.child("Questions").child("Q1").observe(.value, with: { (snapshot)in
if let dataDict = snapshot.value as? [String: Any] {
if let quest = dataDict["Question"] as? String,
let Answers = dataDict["Answers"] as? [String],
let Correct = dataDict["Correct"] as? Int {
quiz.append(Question(q: quest, a: Answers, c: Correct))
}
print (dataDict)
}
})
return quiz
}
}
class Question {
var quest:String
var answers:[String]
var correct:Int
init(q: String, a:[String], c:Int)
{
quest = q
answers = a
correct = c
}
func isCorrectQuestion(itemSelected: String)->Bool {
if (itemSelected == answers[correct]) {
return true
} else {
return false
}
}
}
import UIKit
import Firebase
import FirebaseAuth
class QuestionsViewController: UIViewController, UIPickerViewDelegate {
#IBOutlet weak var usernamelabel: UILabel! //sets username label
#IBOutlet weak var Next: UIButton! //next button
#IBOutlet weak var itemLabel: UILabel! //item user has selected
#IBOutlet weak var Question: UILabel! //sets question label
#IBOutlet weak var pickerview: UIPickerView! //sets picker view
public var totalQuestions: Int = 0 //sets total question to 0
public var currentQuestion = 0 //sets current question to 0
public var totalCorrect: Int = 0 //sets totalcorrect to 0
var itemSelected: String = "" //item selected
var LabelText = String()
let Exam = QuestionList() //uses the questions class for instances
var Questions = QuestionList.getDummyQuestions()
var ref: FIRDatabaseReference!
var refHandle: UInt!
override func viewDidLoad() {
super.viewDidLoad() //when the app is loaded
ref = FIRDatabase.database().reference() //reference
refHandle = ref.child("Questions").observe(.value, with: { (snapshot)in
let dataDict = snapshot.value as! [String: AnyObject]
print (dataDict)
})
usernamelabel.text = LabelText //username
pickerview.delegate = self
itemLabel.text = "" //loads the item label of whats selected
itemSelected = QuestionList.getDummyQuestions()[currentQuestion].answers[0] //initially when loaded first item is selected
Question.text = QuestionList.getDummyQuestions()[currentQuestion].quest
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1 //return one component from the picker
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return QuestionList.getDummyQuestions()[currentQuestion].answers.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?{
return QuestionList.getDummyQuestions(). [currentQuestion].answers[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
itemSelected = QuestionList.getDummyQuestions(). [currentQuestion].answers[row]
}
#IBAction func NextAction(_ sender: Any){
currentQuestion = currentQuestion + 1 //moves onto next question and increments
if (QuestionList.getDummyQuestions()[currentQuestion].isCorrectQuestion(itemSelected: itemSelected)) {
totalCorrect += 1
itemLabel.text = String(totalCorrect) + "/" + String(totalQuestions)
}
if(currentQuestion < QuestionList.getDummyQuestions().count) {
pickerview.reloadAllComponents()
itemSelected = QuestionList.getDummyQuestions()[currentQuestion].answers[1]
Question.text = QuestionList.getDummyQuestions() [currentQuestion].quest
} else {
pickerview.isHidden = true
Question.text = "You have finished"
Next.isHidden = true
}
}
}
Firebase functions do not (and should not) return values as they are asynchronous.
So the return quiz line will fail most of the time as it will try to return data before Firebase has had time to retrieve it from the server.
When coding with Firebase, data is only valid inside the closure following the function. So for example, this what NOT to do:
func someFunc() {
ref.child("Questions").child("Q1").observe(.value, with: { snapshot in
print(snap)
})
print(snap) //this will not print the snap as this line executes *before* the closure
}
So doing it the right way; retrieve the data from Firebase, populate the array and refresh the tableview all within the closure.
static func populateArrayAndRefreshTableView()
{
var ref: FIRDatabaseReference!= FIRDatabase.database().reference()
let questionsRef = ref.child("Questions")
questionsRef.child("Q1").observeSingleEvent(of: .value, with: { snapshot in
if let dataDict = snapshot.value as? [String: Any] {
let quest = dataDict["Question"] as? String,
let Answers = dataDict["Answers"] as? [String],
let Correct = dataDict["Correct"] as? Int {
self.quizArray.append(Question(q: quest, a: Answers, c: Correct))
self.tableView.reloadData()
}
})
}
}
Also note that the original code was using observe(.value). That will leave an observer attached to the ref and if the question changes, the code will be called. It doesn't look like that should be the behavior so using observeSingleEvent will call it once without adding an observer.
Finally - you may want to re-consider how the nodes are named in your structure. It's often best practice to disassociate node name keys from the data they contain.
questions
-UYiuokoksokda
question: "What significant contribution to bioengineering was made on the Loonkerian outpost on Klendth?"
correct_answer: answer_1
answers:
answer_0: "Left handed smoke shifter"
answer_1: "The universal atmospheric element compensator"
answer_2: "Warp coil nullification amplifier"
answer_3: "H.A.L. 9000"
-YY8jioijasdjd
question: "What is Kiri-kin-tha's first law of metaphysics?"
correct_answer: answer_2
answers:
answer_0: "No matter where you go, there you are"
answer_1: "Only people with sunroofs use them"
answer_2: "Nothing unreal exists"
answer_3: "Gravity is heavy"
The keys, UYiuokoksokda, are created with childByAutoId().
If you need to query answers you may want to even denormalize them into their own node and use the question key as the node key for the answers or keep a child node with the question key.

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.