Swift Core Data Quirk - swift

Can anyone help me figure out why this code is behaving thusly...
When opening the app for the first time a "User" is created (if it doesn't already exist, which it doesn't the first time) and then the user is saved along with his/her golf "clubs". I get confirmation of the user saved and the clubs saved in the console. HOWEVER, when I close the app and reopen it the user is fetched but the clubs are not. What am I missing here? Let me know if you need/want to see any screen captures beyond this code...
//MARK: Core Data Variables
var user : User!
var userClubs = NSMutableSet()
var currentRound : Round!
var managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
func prepareUser() {
let fetchRequest = NSFetchRequest(entityName: "User")
let sortDescriptor = NSSortDescriptor(key: "createdTime", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let fetchResults = self.managedObjectContext.executeFetchRequest(fetchRequest, error: nil) as? [User] {
if fetchResults.count > 0 {
self.user = fetchResults[0]
println("First user: \(self.user!.firstName) \(self.user!.lastName)")
let fetchRequestClubs = NSFetchRequest(entityName: "Club")
if let fetchResults2 = self.managedObjectContext.executeFetchRequest(fetchRequestClubs, error: nil) as? [Club] {
if fetchResults2.count > 0 {
println("test: \(fetchResults2[0].type)")
}
}
} else {
println("No user yet")
var newUser : User = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: self.managedObjectContext) as! User
newUser.createdTime = NSDate()
managedObjectContext.save(nil)
var i = 0
println("before array: clubsArray is \(clubsArray.count) clubs long")
var clubs = NSMutableSet.mutableSetValueForKey("clubs")
for newClub in clubsArray {
var club : Club = NSEntityDescription.insertNewObjectForEntityForName("Club", inManagedObjectContext: self.managedObjectContext) as! Club
club.type = clubsArray[i].type as String
club.estimatedMinYardage = clubsArray[i].minDistance as Int
club.estimatedMaxYardage = clubsArray[i].maxDistance as Int
club.lowerBound = clubsArray[i].lowerBound as Int
club.upperBound = clubsArray[i].upperBound as Int
//userClubs.addObject(club)
managedObjectContext.save(nil)
//club.setValue(newUser, forKey: "user")
println("\(club.type)")
i++
}
//user.setValue(userClubs, forKey: "clubs")
prepareUser()
}
}
}
Here's the console output from the first run:
No user yet
before array: clubsArray is 17 clubs long
Putter
LW
SW
PW
9i
8i
7i
6i
5i
5W
... [the rest of the clubs]
First user: Your Name
test: 7i
And from the second run after closing and reopening the app:
First user: Your Name

Bloody hell. I was trying to set 1 of the 17 instances of the Club class to a value that was less than it's minimum value allowed in the data model. If anyone else is having a similar issue check your min/max and default in addition to what #Tom-Harrington suggested in the comments of the original question. Dunno if Apple could get some kind of warning for this kind of thing into future versions of XCode.

Related

How to get an array from Firestore document on Swift 5?

Please help me understand the logic reading data from the Firestore document, if one of the values is an array. I tried other answers here and sources but never came to a simple working way and understand clearly. Firestore document structure — example. And Swift class targets for saving (conditional):
struct MyStruct {
var name: String
var pages: Int
}
let part1 = [MyStruct]()
let name1 = ""
let pages1 = 0
let part2 = [MyStruct]()
let name2 = ""
let pages2 = 0
func readFirestore() { }
What should the document reader function look like to add data to existing targets in the class? Thanks in advance for any help in improving my understanding!
They helped to deal with familiar, thank you for what they are. As expected, everything is simple. But for the beginner there is nothing more difficult than simplicity 😁
func readFirestore() {
self.db.collection("example").document("book").getDocument { (document, error) in
if error == nil {
if document != nil && document!.exists {
//get all document data
guard let documentData = document!.data() else {return}
//get value-array for key "part1"
let element = documentData["part1"] as? [Any] //print -> Optional([name1, 100])
//get first element in array
guard let nameDB = element?[0] as? String else {return} //print -> name1
guard let pagesDB = element?[1] as? String else {return} //print -> 100
//append in class
part1.append(MyStruct(name: nameDB, pages: pagesDB))
name1 = nameDB
pages1 = pagesDB
}
}
}
}

Swift Firebase Firestore userlist (too many reads)

I'm trying to find a solution for a little problem I have with my app. It's a chess app and it works with Firebase and is written in swift. I use the database for user authentication, user information and uploading moves to play against each other online. All the userInfo is saved in a document in the collection "allUsers".
Everything is working fine, but I have a user screen where you can press a refresh button to update the current online users, with the .getDocuments() function. The problem is that every time a user refreshes, they query through all of the registered accounts, check if they have the flag "isOnline = true" and then list only those users in a TableView. I believe this counts in firebase as 1 read for every registered account, even though the user is not online.
The app is already live in the AppStore and I have quite a few people registered already. About 300. But, to be honest a lot of people just try it once or a few times and then leave again and never use the app again. But every time someone wants to update the online users, they cycle through 300 users and this gives me 300 reads with firebase. Right now it's not a big problem, but once people really start to use the app, I will reach my quotum quite quickly.
I could try .addSnapshotListener , but this will refresh the user screen everytime something happens in the userlist. It will be too chaotic. I've read about reading data from cache, but I'm not sure how to go about this. I could also get a fresh userlist when the app starts, save it locally and check every now and then if there are new online users, but I want the list to be updated whenever the user wants to.
Is there a way to compare a locally saved list to the online database list and only read/get the documents that are changed / new?
Sorry for the long text. Hopefully anyone can help me out!
Thanks.
Below is my code to load the users. It's a bit messy sorry.. Basically it retrieves all users and sorts them by online and offline. If a user searches for another user, it takes a String "query" as input.
Code :
func loadAllAvailableUsers(query : String) {
availableEmails = []
availableUsers = []
onlineUsers = []
isInGameIndex = []
var av : [String] = []
var ae : [String] = []
var wins : [Int] = []
var losses : [Int] = []
var draw : [Int] = []
var matches : [Int] = []
let collection = db.collection("allUsers")
collection.getDocuments { (snapshot, err) in
if let e = err {
print(e.localizedDescription)
} else {
if let documents = snapshot?.documents {
print("found users")
for doc in documents {
print(doc.documentID)
if doc.documentID != currentEmail && query == "" {
print(doc.data()["isOnline"] as! Bool)
if let name = doc.data()["username"] as? String, let w = doc.data()["wins"] as? Int, let l = doc.data()["losses"] as? Int, let d = doc.data()["draw"] as? Int, let numOfMatches = doc.data()["numberOfMatches"] as? Int, let online = doc.data()["isOnline"] as? Bool {
print("adding user : \(name) to list")
if online {
matches.append(numOfMatches)
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
onlineUsers.append(name)
if doc.data()["isInGame"] as! Bool == true {
print(i)
self.delegate?.addToInGameIndex(name: name)
}
}
}
} else if query != "" {
if let name = doc.data()["username"] as? String, let w = doc.data()["wins"] as? Int, let l = doc.data()["losses"] as? Int, let d = doc.data()["draw"] as? Int, let online = doc.data()["isOnline"] as? Bool, let numOfMatches = doc.data()["numberOfMatches"] as? Int {
print("Searched : adding user : \(name) to list")
if doc.documentID == currentEmail {
continue
}
let lowerName = name.lowercased()
let lowerQuery = query.lowercased()
if lowerName.contains(lowerQuery) && online {
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
matches.append(numOfMatches)
if doc.data()["isInGame"] as! Bool == true {
print(i)
self.delegate?.addToInGameIndex(name: name)
}
onlineUsers.append(name)
} else if lowerName.contains(lowerQuery) && !online {
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
matches.append(numOfMatches)
}
}
}
}
}
if ae.count > 0 {
self.delegate?.reloadTheTable(wins: wins, losses: losses, draw: draw, ae: ae, au: av, matches: matches)
}
print(availableUsers)
}
}
}

Append model to array in Swift only printing last item added

Im not sure what I am missing here, but it "looks" right to me.
I have the following two variables at the top of my view controller:
var itemSpecifics: ItemSpecifics!
var itemSpecificsArray = [ItemSpecifics]()
I make a call into a API and do the following when its successful:
self.itemSpecifics = ItemSpecifics()
self.itemSpecificsArray.removeAll()
Here is the area where I am getting information and adding it into itemSpecifics.
if let getItemSpecifics = item["ItemSpecifics"] as? NSDictionary {
if let getNameValueList = getItemSpecifics["NameValueList"] as? NSArray {
print("game value list count \(getNameValueList.count)")
for i in 0..<getNameValueList.count {
if let getName = getNameValueList[i] as? NSDictionary {
if let itemName = getName["Name"] as? String {
print(itemName)
self.itemSpecifics._itemSpecificName = itemName
}
if let getValue = getName["Value"] as? NSArray {
let itemValue = getValue[0] as? String
self.itemSpecifics._itemSpecificValue = itemValue!
print("-- \(itemValue!)")
}
}
//End The Condition Information
self.itemSpecificsArray.append(self.itemSpecifics)
}
}
}
So, its being printed correctly.
Professionally Graded
-- Not Graded
Sport
-- Baseball-MLB
Product
-- Lot
Player
-- Derek Jeter
Team
-- New York Yankees
Card Manufacturer
-- Topps
League
-- Major Leagues
Era
-- Modern (1981-Now)
Original/Reprint
-- Original
However, when I do this:
for i in 0..<self.itemSpecificsArray.count {
print(self.itemSpecificsArray[i].itemSpecificName)
print("** \(self.itemSpecificsArray[i].itemSpecificValue)")
}
It prints the following:
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Original/Reprint
** Original
Here is my class for ItemSpecifics.
class ItemSpecifics {
var _itemSpecificName: String!
var _itemSpecificValue: String!
var itemSpecificName : String {
if _itemSpecificName == nil {
_itemSpecificName = ""
}
return _itemSpecificName
}
var itemSpecificValue : String {
if _itemSpecificValue == nil {
_itemSpecificValue = ""
}
return _itemSpecificValue
}
}
What have I missed?
You only have a single instance of ItemSpecifics - you keep updating the properties of that one instance and adding it to the array, so in the end your array holds multiple references to the one instance and that one instance has the last values you assigned.
You can make your code much more "Swifty" - Use a struct rather than a class, which gives immutability; You shouldn't generally use that form of "_" private properties; you can just make a property read-only, but with a struct you won't need to any way.
Also, don't use NS... foundation classes in Swift unless it is unavoidable.
struct ItemSpecifics {
let name: String
let value: String
}
if let getItemSpecifics = item["ItemSpecifics"] as? [String:Any],
let getNameValueList = getItemSpecifics["NameValueList"] as? [[String:Any]] {
for nameValueList in getNameValueList {
if let name = nameValueList["Name"] as? String,
let value = nameValueList["Value"] as? String {
let newItem = ItemSpecifics(name: name, value: value)
self.itemSpecificsArray.append(newItem)
}
}
}

Populate a Chart from firebase database

I've been stuck all day, I'm trying to implement a chart on my app that has to collect some data from my firebase database to use them to populate the chart.
I've try this code to get the data from the database:
let userID = Auth.auth().currentUser?.uid
let bslChecksDB = Database.database().reference().child("BSL Checks").child(userID!)
bslChecksDB.observe((.childAdded)) { (snapshot) in
print(snapshot)
let snapshotValue = snapshot.value as! [String : [String: String]]
print(snapshotValue)
self.arrayOfDic = Array(snapshotValue.values)
for i in 0...self.arrayOfDic.count - 1 {
self.snapshotDictionary = self.arrayOfDic[i]
let date = self.snapshotDictionary["Date"]!
let time = self.snapshotDictionary["Time"]!
let bsl = Double(self.snapshotDictionary["BSL"]!)
let unit = self.snapshotDictionary["Unit"]!
let dateTime = "\(date) at: \(time)"
self.dateTimeArray.append(dateTime)
self.bslDataArray.append(bsl!)
self.uuuuDataArray.append(unit)
}
I try to print the various arrays to see if they were made and they were, so no problem (i thought). then when I proceed to upload those data on the chart, they disappear.... It seems like my arrays are wiped out as soon as they are made! What is wrong?
for i in 0...self.dateTimeArray.count - 1 {
ChartFormatter().array.append(self.dateTimeArray[i])
}
print("array")
print(ChartFormatter().array)
}
var lineChartEntry = [ChartDataEntry] ()
print(dateTimeArray.count)
for i in 0...dateTimeArray.count - 1 {
let value = ChartDataEntry(x: Double(i), y: bslDataArray[i])
lineChartEntry.append(value)
}
let line1 = LineChartDataSet(values: lineChartEntry, label: "BSL")
line1.colors = [NSUIColor.blue]
let data = LineChartData ()
data.addDataSet(line1)
chtChart.data = data
chtChart.chartDescription?.text = "My graph"
Since this is the first time I implement a chart on my app, could you please explain how to populate the chart with data from a firebase database? thanks

databaseReference.observe(DataEventType.value, with:{(DataSnapshot) not working properly all the time

func checkPaid(utilityId : String) -> Int{
var amount:String = ""
var status = 0
print("inside new function ")
print ("\(utilityId) inside new function ")
self.databaseRefPayment.observe(DataEventType.value, with:{(DataSnapshot) in
if DataSnapshot.childrenCount > 0 {
for payments in DataSnapshot.children.allObjects as! [DataSnapshot]{
var paymentsObject = payments.value as? NSDictionary
/*
if(paymentsObject!["month"] as! String == monthCheck && paymentsObject!["year"] as! String == monthCheck && paymentsObject!["utilityid"] as! String == utilityId as! String){ */
if(paymentsObject!["utilityId"] as! String == utilityId){
amount = paymentsObject!["amount"] as! String
print(amount)
print("Ypur program is working perfect")
status = 1
}
}
}
})
return status
}
The above function is filtering the data present in payments node based on the value for utilityId getting passed in the function . But the strange thing is observe(DataEventType.value, with:{(DataSnapshot) this event is not getting triggered all the time . Its just skipping that portion unnecessarily . I am very new to firebase and getting really mad with these kind of unpredicted behaviours . Please help me in this . feel free to ask for any clarifications .
The firebase executes firebase query functions in different thread , so after u call check paid(), it runs the checkpaid() firebase query in another thread,and it will return from the function , eventhough ur query is running in the background..so it will seem like,checkpaid() is not working , but actually it's running on another thread.
I think you first fetch all the required data from payment, and store it in a list , and then use that list to compare with utility.
Every time this function is called it adds/resets the Key-Value Observer for whichever child node you are observing it doesn't actually check the value unless it is changed. I believe it is your intention to call checkPaid(utilityId:) to check the child is 'paid' by some means. There is no need to add a KVO if you are directly reading the value for a single snapshot. consider the following:
func checkPaid(utilityId: String) -> Bool {
//Assume it is not paid if we cannot verify it.
var isPaid = false
//Create a new reference to Firebase Database
var ref: DatabaseReference!
ref = Database.database().reference().child(utilityId)
//Get the values for the child, test if it is paid or not.
ref.queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot) in
if (snapshot.value is NSNull) {
print("No Child With \(utilityId) Exists")
} else {
//child with utilityId exists, in case multiple utilityId's exist with the same value..
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let values = child.value as? [String : AnyObject] {
let uid = child.key //utilityId
var month:String = ""
var year:String = ""
var amount:String = ""
//var amount:Double = 0.0
//get values from parent
if let m = values["month"] as? String {
month = m
}
if let y = values["year"] as? String {
year = y
}
if let a = values["amount"] as? String {
amount = a
}
/*
if let a = values["amount"] as? Double {
amount = a
}
*/
//??
if ((month == monthCheck) && (year == monthCheck)) {
isPaid = true
}
}
}
}
return isPaid
}
I am making one assumption here; that utilityId is the key for the child.
if you have parent nodes to utilityId you'll have to transverse those as well when you reference the database:
ref = Database.database().reference().child(utilities).child(utilityId) ..etc
If you need a KVO to update a local property I suggest adding/calling it in viewDidLoad, it's completion handler should take care of updating whichever properties are updated when they change in Firebase.