Cannot subscript a value of type '[Any]' with an index of type 'String' - swift

I am retrieving data from firestore and so far everything is successful. This is the result in my console.
[{
Name = Tuna;
Price = "$3.6"; }, {
Name = Snapper;
Price = "$25.60"; }]
I want to store Name and Price into variables so that I can display them in my table view. As shown in the code bellow when i set nameOfItem to the Data2, i get an error that states, "Cannot subscript a value of type '[Any]' with an index of type 'String'". I was wondering if someone can help me fix this!
override func viewWillAppear(_ animated: Bool) {
itemCollectionRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
}else {
guard let snap = snapshot else {return}
for document in snap.documents {
let data2 = document.data()["Items"]! as? Array ?? []
print(data2)
let RandomVariable = data2[0]
print(RandomVariable)
let nameOfItem = data2["Name"] as? String ?? ""
let priceOfItem = data2["Price"] as? String ?? ""
//let priceOfItem = data2["Price"] as? String ?? ""
//print(nameOfItem, priceOfItem)
}
}
}

As you can see for yourself there is no such thing as an "Items" element in your console output instead as the error message says you have an array so change your code inside the for loop to
let data = document.data() as? [Any] ?? []
for item in data {
if let dictionary = item as? [String: String] {
let nameOfItem = dictionary["Name"] ?? ""
let priceOfItem = dictionary["Price"] ?? ""
//...
}
}

Related

Get data from firestore and assign it to an array of dictionaries

I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}

How to get an Dictionary from Firebase Firestore in Swift

I wonder how to get an Dictionary from my Firestore. For normal Arrays I've done it like that:
func returnArray(){
let newpath = Firestore.firestore().collection(path)
newpath.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.Array = documents.map { (queryDocumentSnapshot) -> String in
let data = queryDocumentSnapshot.data()
let Name = data["myField"] as? String ?? ""
return Name
}
}
}
That works perfectly fine. My question is now how I have to change my code that its getting an Array out of the Firebase. I had an idea, but it doesn't work:
func returnDictionary(){
let newpath = Firestore.firestore().collection(path)
newpath.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.Dictionary = documents.map { (queryDocumentSnapshot) -> // I don't know what to fill here
in
let data = queryDocumentSnapshot.data()
let Name = data["AnzeigeName"] as? String ?? ""
let ID = data["selfID"] as? String ?? ""
return ID: Name
}
}
}
What can I try next?
.map is going to give you an array, but you can use Dictionary's init(uniqueKeysWithValues:) to turn this into a Dictionary:
let dictionary : Dictionary<String,String> = .init(uniqueKeysWithValues: documents.compactMap { queryDocumentSnapshot -> (String,String)? in
let data = queryDocumentSnapshot.data()
if let name = data["AnzeigeName"] as? String, let id = data["selfID"] as? String {
return (id, name)
}
return nil
})
It's important to know that uniqueKeysWithValues will crash if the keys are not in fact unique, so you'd want to check for that first (look at #New Dev's comment about init(_:uniquingKeysWith:) to handle this). I'm also using compactMap to get rid of nil values.
(Note: In Swift, generally variable names are lowercased and type names are uppercased. Going against that pattern can make your code challenging to read for others)
Firestore Example
{
name: "Anonymous"
pet {
name: "Max"
age: "3"
}
}
Query
Firebase.firestore().collection(path).getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
} else {
for document in querySnapshot!.documents {
let data = document.data()
let name = data["name"]
let pet = data["pet"] as! Dictionary<String, String>
print("Name: \(name)")
print("Pet-name: \(pet["name"]!)")
print("Pet-age: \(pet["age"]!)")
}

Using Firebase in Swift, how can I pull documents from a collection to be displayed in a text view?

After logging into the app, in a settings page, SettingsViewController, I would like for the user to be able to see their information in a text view. However, no matter the approach to reading the data, i always get the Swift Compiler Warning:
Cast from '[String]' to unrelated type 'String' always fails
This is my code:
func textViewFill() {
let db = Firestore.firestore()
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let firstName = ["firstName"] as? String ?? ""
let lastName = ["lastName"] as? String ?? ""
let email = ["email"] as? String ?? ""
self.firstNameTextView.text = firstName
self.lastNameTextView.text = lastName
self.emailTextView.text = email
}
}
}
}
The warning appears for when I am trying to define the documents as strings:
let firstName = ["firstName"] as? String ?? ""
let lastName = ["lastName"] as? String ?? ""
let email = ["email"] as? String ?? ""
Is there no way to display it as text?
You're trying to cast an Array as String here. Here's what you need:
let firstName = document.get("firstName") as? String ?? ""
let lastName = document.get("lastName") as? String ?? ""
let email = document.get("email") as? String ?? ""
You can use if let to unwrap optionals and dont cast Array as string .. it will always fails..
func textViewFill() {
let db = Firestore.firestore()
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let document = querySnapshot!.documents.first {
print("\(document.documentID) => \(document.data())")
if let firstName = document.get("firstName") as? String ,
let lastName = document.get("lastName") as? String,
let email = document.get("email") as? String {
self.firstNameTextView.text = firstName
self.lastNameTextView.text = lastName
self.emailTextView.text = email
}
}
}
}
}

facing Issue in parsing in swift3

I am trying to parse the emergency data in into emergency struct but it never statifies the condition and get into else case.Here is my code and structure.Some thing i have written woring in first line.
if let emergencyDict = snapshotValue["emergency"] as? [String:[String:Any]]{
for (emerId, emerData) in emergencyDict {
let emer = Emergency.init(emergency: emerData as NSDictionary)
emergency.append(emer)
}
}
else{
let emer = Emergency.init(emerg: "" as AnyObject)
emergency.append(emer)
}
struct Emergency{
var emer_id: String
var emer_name: String
var emer_phoneNo: String
init(emergency: NSDictionary) {
if emergency.object(forKey: "id") != nil {
emer_id = emergency.object(forKey: "id") as! String
}
else{
emer_id = ""
}
}
}
The problem you are having emergency as Array with type [Any] and if you remove the first object then you get Array of type [[String:Any]]. So try like this way.
if let array = snapshotValue["emergency"] as? [Any],
let emergencyArrar = Array(array.dropFirst()) as? [[String:Any]] {
print(emergencyArray)
for emergency in emergencyArray {
print(emergency)
}
}
You have written wrong in this line:
if let emergencyDict = snapshotValue["emergency"] as? [String:[String:Any]]{
It should be:
if let emergencyDict = snapshotValue["emergency"] as? [[String:Any]]{
This question should belong to query from firebase database.
// you have to get the children in emergency,
// then get the value(dictionary) of each child
ref.child("emergency").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let keys = value?.allKeys // [1, 2, 3 ....]
for key in keys {
ref.child("emergency").child(key)..observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// Here is your dictionary
}
}
}) { (error) in
print(error.localizedDescription)
}

possible to cast this Alamofire result to an array of dictionaries

I am not an iOS dev and have to make a few changes to a Swift / AlamoFire project (not mine) and am a bit lost.
I have the following JSON:
{"metro_locations":
[
{
"name":"Ruby Red"
},
{
"name":"Blue Ocean"
}
]
}
class (I know that there are issues here):
class Location{
var name=""
init(obj:tmp){
self.name=tmp["name"]
}
}
and need to make an AlamoFire call
Alamofire.request(.GET, "https://www.domain.com/arc/v1/api/metro_areas/1", parameters: nil)
.responseJSON { response in
if let dataFromNetworking = response.result.value {
let metroLocations = dataFromNetworking["metro_locations"]
var locations: [Location]=[]
for tmp in metroLocations as! [Dictionary] { // <- not working, Generic Paramter 'Key' could not be inferred
let location=Location.init(obj: tmp)
locations.append(location)
}
}
}
I have included the error msg, the "not working" but feel that there are issues in other parts too (like expecting a dictionary in the initialization). What does the 'Key' could not be inferred mean and are there other changes I need to make?
edit #1
I have updated my Location to this to reflect your suggestion:
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] else { return nil }
guard let name = dictionary["name"] else { return nil }
guard let latitude = dictionary["latitude"] else { return nil }
guard let longitude = dictionary["longitude"] else { return nil }
self.name = name as! String
self.id = id as! Int
self.latitude = latitude as! Double
self.longitude = longitude as! Double
}
but I get the error:
Could not cast value of type 'NSNull' (0x10f387600) to 'NSNumber' (0x10f77f2a0).
like this:
I would think that the guard statement would prevent this. What am I missing?
You can cast metroLocations as an array of dictionaries, namely:
Array<Dictionary<String, String>>
Or, more concisely:
[[String: String]]
Thus:
if let dataFromNetworking = response.result.value {
guard let metroLocations = dataFromNetworking["metro_locations"] as? [[String: String]] else {
print("this was not an array of dictionaries where the values were all strings")
return
}
var locations = [Location]()
for dictionary in metroLocations {
if let location = Location(dictionary: dictionary) {
locations.append(location)
}
}
}
Where
class Location {
let name: String
init?(dictionary: [String: String]) {
guard let name = dictionary["name"] else { return nil }
self.name = name
}
}
Clearly, I used [[String: String]] to represent an array of dictionaries where the values were all strings, as in your example. If the values included objects other than strings (numbers, booleans, etc.), then you might use [[String: AnyObject]].
In your revision, you show us a more complete Location implementation. You should avoid as! forced casting, and instead us as? in the guard statements:
class Location {
let id: Int
let name: String
let latitude: Double
let longitude: Double
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let name = dictionary["name"] as? String,
let latitude = dictionary["latitude"] as? Double,
let longitude = dictionary["longitude"] as? Double else {
return nil
}
self.name = name
self.id = id
self.latitude = latitude
self.longitude = longitude
}
}