I'm using FB Login for my app in Swift and when I make a graph request, it returns the following result:
Optional({
email = "arjun.ramjams#gmail.com";
id = 10218497873670001;
name = "Arjun Ram";
})
Now, how should I read, each individual value(i.e: email, id and name)?
func fetchProfile() {
let parameters = ["fields": "id,email, first_name, last_name"]
GraphRequest(graphPath: "me",parameters: parameters).start{(connection, user, Err) in
if Err != nil {
print(Err!)
return
}
let dic = user as! NSDictionary
let userID = dic["id"] as! String
let Email = dic["email"] as! String
let fname = dic["first_name"] as! String
let lname = dic["last_name"] as! String
}
}
The result is a Dictionary of type [String:Any]?.
let result:[String:Any]? = [
"email":"arjun.ramjams#gmail.com",
"id" : 10218497873670001,
"name":"Arjun Ram"
]
You can fetch the fields from result like,
let email = result?["email"] as? String
let id = result?["id"] as? Int
let name = result?["name"] as? String
You need to use if let or guard let for unwraping an object or variable
e.g.:
if let result = result as? [String: Any] {
print(result[“email”])
}
or
guard let result = result as? [String: Any] else {
// return something
}
Related
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)
})
}
I mostly work with dictionaries since I am fairly new but here we have an embedded struct that I need to loop through. With this, I need to be able to populate expanding cells in UITableView.
My struct looks like this:
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
And my query looks like this:
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
for document in documentSnapshot!.documents {
let eMail = document.get("E-mail")
let message = document.get("Message")
let timeStamp = document.get("Time_Stamp")
let userEmail = document.get("User_Email")
let planDetails = document.get("planDetails")
// how to loop through planDetails
}
}
I have gotten this far:
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
guard let planDetails = document.get("planDetails") as? [[String : Any]] else {
let email = document.get("E-mail")
let message = document.get("Message")
let timeStamp = document.get("Time_Stamp")
let userEmail = document.get("User_Email")
let status = false
}
for plan in planDetails {
if let menuItemName = plan["menuItemName"] as? String {
// not sure I populate the menuItemsName, menuItemsQuantity and menuItemsPrice within the array
}
}
}
}
}
UPDATE 2:
Final query: This is finally working as expected. But am not able to populate the expanding cells.
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let message = document.get("Message") as? String,
let timeStamp = document.get("Time_Stamp") as? String,
let userEmail = document.get("User_Email") as? String,
let status = document.get("status") as? Bool else {
continue
}
for plan in planDetails {
guard let menuItemName = plan["menuItemName"] as? String,
let menuItemQuantity = plan["menuItemQuantity"] as? Int,
let menuItemPrice = plan["menuItemPrice"] as? Double else {
continue
}
let planDetails = PlanDetails(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
let complaint = Complain(eMail: email, message: message, timeStamp: timeStamp, userEmail: userEmail, status: status, planDetails: planDetails)
self.messageArray.append(complaint)
print(self.messageArray)
}
}
}
}
EDIT
// OPTION 1 - FULL REQUIREMENT
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
// all of these properties are required to parse a single document
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let message = document.get("Message") as? String,
let timeStamp = document.get("Time_Stamp") as? String,
let userEmail = document.get("User_Email") as? String,
let status = document.get("status") as? Bool else {
continue // continue this loop
}
for plan in planDetails {
// all of these properties are required to instantiate a struct
guard let name = plan["menuItemName"] as? String,
let price = plan["menuItemName"] as? Double,
let quantity = plan["menuItemQuantity"] as? Int else {
continue // continue this loop
}
let plan = PlanDetails(menuItemName: name, menuItemQuantity: quantity, menuItemPrice: price)
// then you will likely append this plan to an array
}
}
}
}
// OPTION 2 - PARTIAL REQUIREMENT
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
// all of these properties are required to parse a single document
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let message = document.get("Message") as? String else {
continue // continue this loop
}
// these properties are not required
let timeStamp = document.get("Time_Stamp") as? String // optional (can be nil)
let userEmail = document.get("User_Email") as? String // optional (can be nil)
let status = document.get("status") as? Bool ?? false // not optional because it has a default value of false
for plan in planDetails {
// all of these properties are required to instantiate a struct
guard let name = plan["menuItemName"] as? String,
let price = plan["menuItemName"] as? Double,
let quantity = plan["menuItemQuantity"] as? Int else {
continue // continue this loop
}
let plan = PlanDetails(menuItemName: name, menuItemQuantity: quantity, menuItemPrice: price)
// then you will likely append this plan to an array
}
}
}
}
There are a number of other ways you can do this, these are just a couple. For example, you can instantiate the struct with the [String: Any] dictionary itself.
I finally figured out what the problem was. Had to do with the way I setup the struct and how I populate the array.
Right way is as such i.e. the planDetails need to be cast as an array:
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: [PlanDetails]
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
And then I need to iterate through each plan item, add it to the array and then add the array to the top level Complain:
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let documentSnapshot = documentSnapshot {
for document in documentSnapshot.documents {
guard let planDetails = document.get("planDetails") as? [[String : Any]],
let email = document.get("E-mail") as? String,
let status = document.get("status") as? Bool else {
continue
}
for plan in planDetails {
guard let menuItemName = plan["menuItemName"] as? String,
let menuItemQuantity = plan["menuItemQuantity"] as? Int else {
continue
}
let singlePlan = PlanDetails(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity)
self.planDetailsArray.append(singlePlan)
}
let complain = Complain(eMail: email, status: status, planDetails: self.planDetailsArray)
self.planDetailsArray.removeAll()
self.messageArray.append(complain)
}
}
// Print the main array or load table
}
}
bsod's answer stays as the correct answer because without his help, I would not have been able to arrive at his conclusion.
Here is my code and it fails to execute as noted below.
I am trying to cast an object to my custom data type called UserData.
First problem I have is don't understand how to get the value out of the array correctly
Second I cannot seem to cast the object as the type I need, UserData. What I am doing wrong?
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
var users = NSMutableArray();
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
for i in 0 ..< jsonResult.count {
print("loop count :", i );
jsonElement = jsonResult[i] as! NSDictionary
let user = UserData()
//the following insures none of the JsonElement values are nil through optional binding
if let UserID = jsonElement["Userid"] as? String,
let firstName = jsonElement["First_Name"] as? String,
let lastName = jsonElement["Last_Name"] as? String,
let userSessionID = jsonElement["Session_ID"] as? String
{
user.UserID = UserID
user.FirstName = firstName
user.LastName = lastName
user.UserSessionID = userSessionID
print("users firstName:", user.FirstName ?? "blank");
}
users.add(user)
}
print("users size:", users.count); // this shows 2
// So i know I have data loaded... BUT when i try and
// get it then it all goes to heck. See below
// NOT SURE what I am doing here...
// Thought it was java like where I could just get a
// item from the NSMutableArray using an index value
// then cast it as my UserData object
// and print the output... but this does not work
// Why is this so hard??
let userDataVal = users.index(of: 0) as! UserData;
print("firstName:", userDataVal.FirstName);
}
Below is how I would write this function. Some comments
No NS... classes used, instead I use native arrays and dictionaries
If JSONSerialization.jsonObject generates an error the function is exited
Local variables are define as close as possible to as where they are used
Create an init method for your UserData struct/class that takes the values as parameters so you can do let user = UserData(userId: UserId, firstName:... instead.
Name local variables and properties with a first lowercase character, it makes it easier to read the code
func parseJSON(_ data:Data) {
var jsonResult: [[String: Any]]?
do {
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as? [[String: Any]]
} catch let error as NSError {
print(error)
return
}
guard let result = jsonResult else {
return
}
var users = [UserData]()
for jsonElement in result {
if let UserID = jsonElement["Userid"] as? String,
let firstName = jsonElement["First_Name"] as? String,
let lastName = jsonElement["Last_Name"] as? String,
let userSessionID = jsonElement["Session_ID"] as? String
{
var user = UserData()
user.UserID = UserID
user.FirstName = firstName
user.LastName = lastName
user.UserSessionID = userSessionID
users.append(user)
}
}
for user in users {
print("firstName:", user.FirstName);
}
}
Better approach would be to use JSONDecoder() instead of a JSONSerailizer. Try using the following code.
struct User: Codable {
var userId, firstName, lastName, userSessionId: String
enum CodingKeys: String, CodingKey {
case userId = "Userid"
case firstName = "First_Name"
case lastName = "Last_Name"
case userSessionId = "Session_ID"
}
}
func parseJSON(_ data: Data) {
do {
let users = try JSONDecoder().decode([User].self, from: data)
users.forEach { user in
print("users first name:", user.firstName)
}
} catch {
print(error.localizedDescription)
}
}
Here is the output of "print(dict)"...
["2018-10-17 11:19:51": {
firstname = Brooke;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Alvarez;
message = hshahyzhshbsbvash;
username = poiii;
}]
["2018-10-17 11:20:31": {
firstname = Trevor;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Bellai;
message = hey;
username = br9n;
}]
["2018-10-17 11:20:44": {
firstname = Amy;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Ikk;
message = hey;
username = nine9;
}]
My code...
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
print(dict)
// Store data in user.swift model
let firstnameData = dict[0]["firstname"] as! String
let idData = dict["id"] as! String
let lastnameData = dict["lastname"] as! String
let messageData = dict["message"] as! String
let usernameData = dict["username"] as! String
let rankData = dict["rank"] as! String
let propicrefData = dict["propicref"] as! String
let convoinfo = RecentConvo(firstnameString: firstnameData, idString: idData, lastnameString: lastnameData, messageString: messageData, usernameString: usernameData, rankString: rankData, propicrefString: propicrefData)
self.recentconvos.append(convoinfo)
print(self.recentconvos)
self.tableView.reloadData()
}
}
I'm trying to retrieve the dictionary within the first dictionary which is the value to the key which is the date associate with it. For example: 2018-10-17 11:19:51. However I cannot use this exact string to call it because I must do this without the knowledge of that string.
I tried this:
let firstnameData = dict[0]["firstname"] as! String
But it returns an error:
Cannot subscript a value of type '[String : Any]' with an index of type 'Int'
The error noted above is showing up because you were trying to access the element at a certain position (0) from the dictionary. Dictionaries are not ordered lists, and hence won't have a fixed order of elements to be accessed.
The logged dictionary doesn't really look like a dictionary. Assuming that it is a dictionary, and its keys are the date strings, you can use the following code snippet to parse the dictionary.
class RecentConversation {
var id: String?
var firstName: String?
var lastName: String?
var message: String?
var username: String?
var rank: String?
var propicref: String?
init?(dictionary: [String: Any]?) {
guard let dict = dictionary else {
// Return nil in case the dictionary passed on is nil
return nil
}
id = dict["id"] as? String
firstName = dict["firstname"] as? String
lastName = dict["lastname"] as? String
message = dict["message"] as? String
username = dict["username"] as? String
rank = dict["rank"] as? String
propicref = dict["propicref"] as? String
}
}
Usage:
let dateStrings = dict.keys.sorted {
// Sort in chronological order (based on the date string; if you need to sort based on the proper date,
// convert the date string to Date object and compare the same).
//
// Swap the line to $0 > $1 to sort the items reverse chronologically.
return $0 < $1
}
var conversations: [RecentConversation] = []
for date in dateStrings {
if let conversation = RecentConversation(dictionary: (dict[date] as? [String: Any])) {
conversations.append(conversation)
}
}
You were all very helpful, so I would like to start off by saying thank you. I went ahead and applied the method that lionserdar explained. (.allKeys)
// Fetch Recent Messages
func fetchRecentMsgs() {
// Direct to database child
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? NSDictionary {
print(dict)
print(dict.allKeys)
let keys = dict.allKeys
for key in keys {
print(key)
if let nestedDict = dict[key] as? [String: Any] {
print(nestedDict)
let firstnameData = nestedDict["firstname"] as! String
let idData = nestedDict["id"] as! String
let lastnameData = nestedDict["lastname"] as! String
let messageData = nestedDict["message"] as! String
let usernameData = nestedDict["username"] as! String
Worked for me so I hope this will help others too!
How to get body and title data from the below response. I'm totally stuck
[aps: {
alert = {
body = test123;
title = test123;
};
},
gcm.message_id: 0:4a]
If you are working with notification then you need to access the data like this way.
if let info = notification.userInfo as? [String: AnyObject],
let apsDic = info["aps"] as? [String: AnyObject],
let alertDic = info["alert"] as? [String: AnyObject] {
if let body = alertDic["body"] as? String {
print(body)
}
if let title = alertDic["title"] as? String {
print(title)
}
}