Checking if the Firestore key has value - swift

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 ?? ""

Related

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

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

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] ?? [""]

How can I fix this Result values in '? :' expression have mismatching types 'String' and '[Any]?'

I have the following lines of code:
let string:String = ""
let extracted_data:String! = (response?.extracted_data == nil) ? "" : response?.extracted_data
string.append("\(extracted_data)")
extracted_data is a [Any]. I can't figure out why I am getting this error:
Result values in '? :' expression have mismatching types 'String' and '[Any]?'
You should be parsing your variable as a [Any] type of array and later on if you know that your array contains string type you should be doing like this. Take a first element out of array and set your value
response?.extracted_data?.first as? String ?? "Default Value if string is not present"
The values returned by the ternary operator ? : need to be the same type, because Swift needs to know at compile time what type extracted_data will be. You are attempting to return either a String or [Any]?. To fix this, return an empty array [] instead of an empty string "" for the nil case. Also, you need to unwrap response so that response!.extracted_data returns [Any] instead of [Any]?. Although force unwrapping with ! can lead to crashes, this is safe because you already know response is not nil due to your check.
var string: String = "" // This needs to be a var for append to work
let extracted_data = response?.extracted_data == nil ? [] : response!.extracted_data
string.append("\(extracted_data)")
print(string)
Now look at this line:
let extracted_data = response?.extracted_data == nil ? [] : response!.extracted_data
Swift has a better way of doing that with a special nil coalescing operator ??. It is used to unwrap an optional value or to supply a default value for when that value is nil. Using ??, the line becomes:
let extracted_data = response?.extracted_data ?? []
I assume this is a simplified example, because you could just assign to string instead of appending. In either case, string needs to be a var to be modified and its type can be inferred . Here is the final version of the code:
var string = ""
let extracted_data = response?.extracted_data ?? []
string.append("\(extracted_data)")
print(string)
Perhaps you don't want the [] that prints when response == nil. In that case, have ? : return a String like so:
var string = ""
let extracted_data = response == nil ? "" : "\(response!.extracted_data)"
string.append(extracted_data)
print(string)
If you want to print just the first item of your [Any] array, and it can really be of any type, then you can do this:
var string = ""
let first = response?.extracted_data.first
let extracted_data = first == nil ? "" : "\(first!)"
string.append(extracted_data)
print(string)

How to check if a field type Any? is nil o NSNull

I'm actually trying to parse a Json object with Swift3 on Xcode8.1.
This is my code:
if let objData = objJson["DATA"] as! NSDictionary? {
var msg: String = ""
if let tmp = objData.object(forKey: "Message") {
msg = tmp as! String
} else {
print("NIIILLLLL")
}
}
I'm getting this error message: Could not cast value of type 'NSNull' (0x4587b68) to 'NSString' (0x366d5f4) at this line msg = tmp as! String.
I'm not understanding why I'm getting this error because the type of tmp is Any and it should display the print instead of convert tmp as! String
Thank you for the help,
You can add casting in let.
if let tmp = objData.object(forKey: "Message") as? String {
msg = tmp
}
With Swift 3, for example:
fileprivate var rawNull: NSNull = NSNull()
public var object: Any {
get {
return self.rawNull
}
}
You can check field object as:
if self.object is NSNull {
// nil
}
So to answer your question in why you are getting that error, in your code "tmp" is not nil its something of type NSNull (if you want to know more about NSNull check the docs) but its basically "A singleton object used to represent null values in collection objects that don’t allow nil values."
The rest is just you are force casting which I recommend avoiding this is a safer way to do what you are doing.
guard let objData = objJson["DATA"] as? [String: Any], let msg = objData["Message"] else { return }
// now you can use msg only if exists and also important keeping its unmutable state

'(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)