How to retrieve an int-type data from firebase to xcode? (Error: Cannot convert value of type 'String' to expected argument type 'Int') - swift

I was trying to fetch data from firebase to my xcode project. The only fields I have are 'userName', 'age', and 'number'. I was trying to append the values into arrays userNames, ages, and numbers. However, I get the error Error: Cannot convert value of type 'String' to expected argument type 'Int in the appending age and number lines. They both have been entered as Int types in the database.
I thought about converting the values of the database into Int types since I think Xcode is taking them as String types? I tried, but I think my code itself was wrong so I couldn't solve it. I would appreciate the help!
func fetchData () {
let database = Firestore.firestore()
database.collection("users").addSnapshotListener {(snap, Error) in
if Error != nil {
print("Error")
return
}
for i in snap!.documentChanges {
let documentID = i.document.documentID
let userName = i.document.get("userName")
let age = i.document.get("age")
let number = i.document.get("number")
DispatchQueue.main.async {
self.userNames.append("\(userName)")
self.ages.append("\(Int(age))") // error line
self.numbers.append("Int\(number)") // error line
}
}
}
}

You can try to coerce the data into the types you want by using as?. (get returns Any? by default)
let userName = i.document.get("userName") as? String
let age = i.document.get("age") as? Int
let number = i.document.get("number") as? Int
Keep in mind that you'll end up with Optional values, so you'll need to have a fallback in case you don't get the type you expect.
If you wanted to provide defaults, you could do:
let userName = i.document.get("userName") as? String ?? ""
let age = i.document.get("age") as? Int ?? 0
let number = i.document.get("number") as? Int ?? 0
Then, later:
self.userNames.append(userName)
self.ages.append(age)
Note that your compilation errors were happening because you were trying to store String values ("") in an [Int]
In general, rather than storing all of these in separate arrays, you may want to look into making a struct to represent your data and storing a single array with that type. See this answer for an example: https://stackoverflow.com/a/67712824/560942
And, the Firestore documentation on custom objects: https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects

Related

How to get key and value of Firestore mapped object

I have an app where users can rate films they have watched, and I want to be able to put these in a tableView. The rating is stored in Firestore, and I want to put both the KEY and value into a Struct so I can access it for the tableView.
However any site/tutorial/stack question I have seen only gets the Maps value, but not the key (in this case, the title name). I can access the value, but only by using the field key, but that is what I am trying to get (see attempt 1)
Struct:
struct Rating: Codable {
var ratedTitle: String
var ratedRating: Int
}
Variable:
var ratedList = [Rating]()
Load data function (attempt 1):
let dbRef = db.collection("Users").document(userID)
dbRef.getDocument { document, error in
if let error = error {
print("There was an error \(error.localizedDescription)")
} else {
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
let midnightMass = titleRating!["Midnight Mass"]
print("Rating given to Midnight Mass: \(midnightMass!) stars")
}
}
}
//Prints: Rating given to Midnight Mass: 2 stars
Also tried (but I don't know how to get this array onto a tableView and have the first index as the Title label, and the second index a Rating label for each movie in the array) attempt 2:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.userRatedList = titleRating!
print("userRatedList: \(self.userRatedList)")
}
//Prints: userRatedList: ["Midnight Mass": 2, "Bly Manor": 5]
Attempt 3:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.ratedList = [Rating(ratedTitle: <#T##String#>, ratedRating: <#T##Int#>)]
//Don't know what I would put as the ratedTitle String or ratedRating Int.
self.ratedList = [Rating(ratedTitle: titleRating!.keys, ratedRating: titleRating!.values)]
//Cannot convert value of type 'Dictionary<String, Int>.Keys' to expected argument type 'String'
//Cannot convert value of type 'Dictionary<String, Int>.Values' to expected argument type 'Int'
}
Firstly, I am not sure why you need the struct to conform to Codable?
Now, based off what I see, "Title Ratings" is a dictionary with a String key and an Int value. You are overcomplicating this. If you want to access the key and value of each element individually, use a for-in loop.
//Declare your global variable
var ratedList = [Rating]()
//If you are using an if let, there is not need to force unwrap
if let docData = document.data() {
if let userRatingList = docData["Title Ratings"] as? [String: Int] {
for (key, value) in userRatingList {
let rating = Rating(ratedTitle: key, ratedRating: value)
ratedList.append(rating)
}
//reload your tableView on the main thread
DispatchQueue.main.async {
tableView.reloadData()
}
}
}

Why am I receiving a "Cast from '[String]' to unrelated type 'String' always fails" error?

I’m trying to display an array of data (stored within a Firestore database field entitled "product") into a UITableViewController. The code I am currently using to try and return the array into my app is below:
if userId == user?.uid {
let group_array = document["product"] as? Array ?? [""]
let productName1 = (group_array) as? String
self.tableViewData =[cellData(opened: false, title: "Item 1", sectionData: [productName1 ?? "No data to display :("])]
}
When I run this, it compiles, however I’m currently getting the following warning message:
Cast from '[String]' to unrelated type 'String' always fails”
This results in no data being returned, and returning nil for productName1, consequently leading to the "No data to display :(" message being shown instead of the data.
Does anyone know which type I would need to assign for the following line to return this data please?
let productName1 = (group_array) as? String
You've defined group_array as an Array. Then you try to cast it to a String. You can't cast an Array (even an array of String objects) to a String.
This line:
let productName1 = (group_array) as? String
Will always fail.
What result are you looking for? The first entry in the array of strings? A string that combines all the elements in the array together?
If you want the first element, use
let productName1 = group_array.first as? string ?? ""
If you want to combine the items, use something like this:
let productNames = (group_array as? [String])?.joined(separator: ", ") ?? ""
By the way, your code would be cleaner if you cast your array to an array of Strings, not just a generic Array (Array of Any)
Change:
let group_array = document["product"] as? Array ?? [""]
To:
let group_array = document["product"] as? [String] ?? [""]

Checking if the Firestore key has value

I am looping trough a snapshot retrieved from a Firestore database getting values into my custom object with conditional unwrapping like the example below. It works fine as long as the key has a value, but as soon as it is empty i get an exception on line if let temp = document.get("windGust") as! String? (See error further down)
I thought conditional unwrapping was supposed to handle this ?
Can anyone point me in the right direction on how to handle this in code? If the value of windGust is empty it should just ignore it and continue.
db.collection("yrData").getDocuments { (snapshot, error) in
for document in snapshot!.documents {
let yrData = YrData()
if let temp = document.get("windGust") as! String?
{
yrData.windGust = temp
}
The error:
Could not cast value of type '_NSZeroData' (0x7fff87d0b5b8) to 'NSString' (0x7fff87d0eee8).
2020-01-16 21:29:23.417663+0100 Victoria[13603:708774] Could not cast value of type '_NSZeroData' (0x7fff87d0b5b8) to 'NSString' (0x7fff87d0eee8).
Maybe not an answer to my question, but this is a solution:
Instead of using:
if let temp = document.get("windGust") as! String?
{
yrData.windGust = temp
}
Use data() like this:
yrData.windGust = data["windGust"] as? String ?? ""
You may just need to use the null coalescing operator.
Something like that would read; if windGust lookup is not a string, assign an empty string.
yrData.windGust = document.get("windGust") as? String ?? ""

Unable to convert anyObject to String

I am trying to run the following code, but am unable to convert the rest.value without the preceeding "Optional". The following is the code I get when attempting to solve the issue:
//for Withdrawl children
transRef.child("Withdraw").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
//creates enumerator of the snapshot children for
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
let curString = (rest.value) as! String
self.anArray.append(curString)
//printing the rest.value to make sure there are children to iterate through
print(rest.value)
}
self.tableView.reloadData() //<<<<<<<<<<<< RELOAD TABLEVIEW
})
this is the error I am receiving: Could not cast value of type '__NSCFNumber' (0x10d068368) to 'NSString'
According to the Firebase documentation the value property: "Returns the contents of this data snapshot as native types. Data types returned: + NSDictionary + NSArray + NSNumber (also includes booleans) + NSString".
The var returns an Any? in order to handle this versatility.
In this case it looks like it is returning an NSNumber, so the String cast fails. I'm not sure exactly the context of the value in this scenario, but if you are sure that it is going to return a number every time you could cast it to a numerical type (Swift/Objective-C bridging allows NSNumber to be casted to any of the primitive number types in Swift), and then pass it into a String initializer to get a String out of it. e.g.:
let curNumber = rest.value as! Double
let curString = String(curNumber)
If you aren't sure what type will be returned, but are expecting to put it into an Array of Strings then you should probably check before you cast e.g.:
if let curNumber = rest.value as? Double {
let curString = String(curNumber)
self.anArray.append(curString)
}
Instead of (rest.value) as! String, say [Swift 2] String(rest.value) or [Swift 3] String(describing:rest.value). [If rest.value is an Optional, then say rest.value! in those formulations.]

'(key: AnyObject, value: AnyObject)' does not have a member named 'subscript'

I have a problem similar to this question, but the answers there isn't helping me.
I have these lines of code:
var id = item["id"] as? String ?? ""
var name = item["name"] as? String ?? ""
var pic = item["pic"] as? String ?? ""
To me these lines of code a pretty much the same. For Xcode, this is a different matter.
The first line is fine. The second two lines generate this error:
'(key: AnyObject, value: AnyObject)' does not have a member named 'subscript'
Here is some more context for you all:
class func getFromJson(json:NSDictionary) -> [Collection] {
var collections = [Collection]()
if json.count > 0 {
for item in json {
var id = item["id"] as? String ?? ""
var name = item["name"] as? String ?? ""
var pic = item["pic"] as? String ?? ""
var newUser = Collection(id:id, name:name, pic:pic)
collections.append(newUser)
}
}
return collections
}
Can anyone explain to me how to fix this. Bonus points if you can explain why the first line is fine, but the next two nearly identical lines produce errors!
Thanks in advance.
'(key: AnyObject, value: AnyObject)' indicates that item is not an Dictionary but is a Tuple with a single key/value pair.
Iterating dictionaries in swift interates through tuples:
for (key, value) in json {
println(key, value)
}
Your for loop indicates that you are probably wanting a json Array of tuples instead of a json Dictionary.
item["id"] would give you a compile time error if you declared the parameter as a tuple. It seems you stumbled onto something hidden with the language with how either tuples or subscripts work under the hood.
More on Subscripts
More on Types (Tuples)