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)
Related
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
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] ?? [""]
I've been playing about with functional programming in Swift. However, I've encountered a problem.
If I call map on a collection of one type, how do I create a new collection of another type?
var fontFamilyMembers = [[Any]]()
//...
let postscriptNames = fontFamilyMembers.map {
[
"name": $0[0] as! String,
"weight": $0[2] as! Int,
"traits": $0[3] as! UInt
]
}
// Error: Value of type 'Any?' has no member 'count'
lengths = postscriptNames.map { $0["name"].count }
I understand I need to cast $0["name"] as a string. Why do I have to do that when I have already done it above? ("name": $0[0] as! String). Is that because postscriptNames is also type [[Any]]()?
I've tried this:
// Error: Cannot convert value of type 'Int' to closure result type 'String'
fontPostscriptNames = postscriptNames.map { ($0["name"] as! String).count }.joined(separator: "\n")
…but know I'm doubly confused.
How do I get map to return the count of each "name"?
Update
My original question still stands. However, I can avoid the problem altogether by using a struct as opposed to a dictionary which I assume is preferred in Swift.
let postscriptNames = fontFamilyMembers.map {
(
name: $0[0] as! String,
weight: $0[2] as! Int,
traits: $0[3] as! UInt
)
}
lengths = postscriptNames.map { $0.name.count }
Is that because postscriptNames is also type [Any]?
Yes. And as postscriptNames is of type [Any], you need to downcast $0.name to String
lengths = postscriptNames.compactMap { ($0.name as? String).count }
Downcast it to String to get the count.
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 ?? ""
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)