How To Remove and Add Double Nested Elements in Firestore Array - swift

I have a nested Codable Object In another object array in another object. I don't see how I can use FieldValue.arrayRemove[element]. Anyone know to do this? Thanks. I am trying to make it so that I can remove a cardField element in the LevelCard element in the job array.
Here is my code
struct Job: Identifiable, Codable {
var id: String? = UUID().uuidString
var uid: String = ""
var title: String = ""
var description: String = ""
var images: [ImagesForJob] = []
var levelCards: [LevelCard] = []
var tags: [Tag] = []}
struct LevelCard: Identifiable, Codable {
var id = UUID().uuidString
var name: String = ""
var color: String = "A7D0FF"
var fields: [CardField] = []}
struct CardField: Identifiable, Codable {
var id = UUID().uuidString
var name: String = ""
var value: String = ""
var type: FieldType = .Text}
func removeExistingCard(id: String, card: LevelCard) {
var data: [String: Any] = ["":""]
do {
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(card)
data = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
} catch {
print("Error encoding account info\(error.localizedDescription)")
}
db
.collection("listings")
.document(id)
.updateData(["levelCards": FieldValue.arrayRemove([data])]) {err in
if let err = err {
withAnimation {
self.errMsg = "Failed to delete card: \(err.localizedDescription)"
self.showErrMsg = true
}
return
}
self.getUsrLstngs()
}
}
func removeExistingField(id: String, field: CardField) {
var data: [String: Any] = ["":""]
do {
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(field)
data = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
} catch {
print("Error encoding account info\(error.localizedDescription)")
}
db
.collection("listings")
.document(id)
.updateData(["levelCards": FieldValue.arrayRemove([data])]) {err in
if let err = err {
withAnimation {
self.errMsg = "Failed to delete card: \(err.localizedDescription)"
self.showErrMsg = true
}
return
}
self.getUsrLstngs()
}
}
Also, Bonus, Does anyone know how to ignore the "id" variable when encoding all of my objects to Firestore? Thanks again.

Related

How can I fetch data from a Firestore reference?

I have the chat collection that has the following fields: hasUnreadMessage as a Bool, isActive as a Bool, person as a reference to
person collection, messages as an array of references to message collection.
Here are some screenshots
I want to create a function to fetch all the messages but for example in the person reference when I print directly the call of the imgString or name it's correct, but when I add them to the ChatModel they are missing.
Here is the function that I created.
func fetchMessages() {
db.collection("chat").getDocuments { snapshot, err in
if let error = err {
debugPrint("Error fething documents: \(error)")
} else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
var chatMessages: [Message] = []
var chat: ChatModel = ChatModel(person: Person(name: "", imgString: ""), messages: [], hasUnreadMessage: false, isActive: false)
let personRef = (document.get("person") as? DocumentReference ?? nil)?.getDocument(completion: { personSnapshot, personErr in
if let personError = personErr {
debugPrint("Error getting chat person: \(personError)")
} else {
guard let personSnap = personSnapshot else { return }
let personData = personSnap.data()
chat.person.imgString = personData?["imgString"] as? String ?? ""
chat.person.name = personData?["name"] as? String ?? ""
}
})
let messages = document.get("messages") as? [DocumentReference] ?? []
for message in messages {
message.getDocument { messageSnapshot, messageErr in
if let messageError = messageErr {
debugPrint("Error getting message: \(messageError)")
} else {
guard let messageSnap = messageSnapshot else { return }
let messageData = messageSnap.data()
let date = messageData?["date"] as? String ?? ""
let text = messageData?["text"] as? String ?? ""
let type = messageData?["type"] as? String ?? ""
chatMessages.append(Message(text, type: type, date: date))
}
}
}
chat.hasUnreadMessage = data["hasUnreadMessage"] as? Bool ?? false
chat.isActive = data["isActive"] as? Bool ?? false
chat.messages = chatMessages
self.chats.append(chat)
}
}
}
}
Edit:
Here are the outputs. I saw that I called the imgString print before the ChatModel print and they appeared in the opposite order
BT_Tech.ChatModel(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("D195D8D8-F401-4C71-B571-4877E6574B68")), person: BT_Tech.Person(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("46768262-EB88-42B3-A9FF-4F9FF3A7B7F1")), name: "", imgString: ""), messages: [], hasUnreadMessage: true, isActive: true)
"imgString: girl1"
Here is the ChatModel
struct ChatModel: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var person: Person
var messages: [Message]
var hasUnreadMessage: Bool
var isActive: Bool
}
struct Person: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var name: String
var imgString: String
}
struct Message: Identifiable, Codable {
#DocumentID var id: String? = UUID().uuidString
var date: String
var text: String
var type: String
init(_ text: String, type: String, date: String) {
self.text = text
self.type = type
self.date = date
}
init(_ text: String, type: String) {
self.init(text, type: type, date: "")
}
}

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
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
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

convert Codable to Json (Dictionary) Swift

How can I convert Codable to Json Dictionay its a nested struct
struct one
struct User: Codable {
var name = "siddhant"
var age = 12
var topInt = ["1","2","3"]
var moreDetails = MoreDetails()
}
struct Two
struct MoreDetails: Codable {
var image = "ImageUrl"
}
I need out put as a Json when I convert this to Json it just convert me the struct one not the 2nd struct
do {
let sid = try JSONEncoder().encode(users)
let dict = try JSONSerialization.jsonObject(with: sid, options: []) as? [String: Any]
print(dict)
}
catch {
print(error)
}
current output which is wrong:
(["name": siddhant, "topInt": <__NSArrayI 0x2831a1c20>(1,2,3),
"moreDetails": {
image = ImageUrl;
}, "age": 12])
I want output like
{
"name": "siddhant",
"topInt": ["1", "2", "3"],
"moreDetails": {
"image": "ImageUrl"
},
"age": 12
}
You can create an extension on Encodable so that you can use it for all elements that conform to Encodable protocol
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
In your case if the model is user of type User
guard let userDict = user.dictionary else { return }
what I did now is
let users = User()
let staticJson = ["name": users.name,
"age": users.age,
"topInt": users.topInt,
"moreDetails": ["image": users.moreDetails.image]] as? [String: Any]
You need to specify what is your desired result. Following your code:
struct User: Codable {
var name = "siddhant"
var age = 12
var topInt = ["1","2","3"]
var moreDetails = MoreDetails()
var dictionaryJson: [String : Any] {
let encoder = JSONEncoder()
return (try? JSONSerialization.jsonObject(with: encoder.encode(self), options: .allowFragments)) as? [String: Any] ?? [:]
}
}
struct MoreDetails: Codable {
var image = "ImageUrl"
}
do {
let users = User()
let jsonData = try JSONEncoder().encode(users)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
print("\n")
print(users.dictionaryJson)
print("\n")
print("Getting the var from dictionary")
print(users.dictionaryJson["moreDetails"])
} catch {
print(error)
}
The output:
{"age":12,"moreDetails":{"image":"ImageUrl"},"name":"siddhant","topInt":["1","2","3"]}
["topInt": <__NSArrayI 0x6000008e8150>(
1,
2,
3
)
, "name": siddhant, "age": 12, "moreDetails": {
image = ImageUrl;
}]
Getting the var from dictionary
Optional({
image = ImageUrl;
})

Swift - Encode and Decode a dictionary [String:Any] into plist

I am trying to store the dictionary in my class Marker but it is throwing an error saying it is not encodable or decodable. I can see the error is caused by the [String: Any] but how can I go around it?
var buttonActions : [String: [String: [String:Any]]] = [:]
Save and Load
func saveData() {
let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(fileName).plist")
let encoder = PropertyListEncoder()
do {
let data = try encoder.encode(markerArray)
try data.write(to: dataFilePath!)
print("Saved")
} catch {
print("Error Encoding \(error)")
}
}
func loadData() {
let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(fileName).plist")
if let data = try? Data(contentsOf: dataFilePath!){
let decoder = PropertyListDecoder()
do {
markerArray = try decoder.decode([Marker].self, from: data)
} catch {
print("Decode Error \(error)")
}
}
Class
class Marker : Encodable, Decodable {
var UUIDpic: UUID = UUID()
var alpha: Int = 1
var buttonType: Int = 0
var buttonActions : [String: [String: [String:Any]]] = [:]
var buttonNameColor: String = ""
var buttonNameFontSize: Int = 10
var buttonShape: String = ""
var loggerRect: String = ""
var maskColor: String = ""
var name: String = ""
}
Unfortunately you cannot use encode or decode on generic types containing Any (e.g. [String: Any] or [Any]). Any does not conform to protocols Encodable nor Decodable and Swift doesn't know how to encode/decode it. You must use a concrete generic type for your dictionary (e.g. [String: String]).
If you still need to use a general type like Any you have to implement encode(to:) and init(from:) methods. Another option would be to use a struct instead of your [String: [String: [String:Any]]] which conforms to Codable (Encodable & Decodable). You will still have to implement encode(to:) and init(from:) methods in that struct, but the bright side is that you will not have to write the encoder.encode() story for all the properties like you would have to if you implement them in the Marker class.
So finally worked it out with the help of Andrada.
I added a second struct which held the action and by passed having to use [string:any]
class Marker : Encodable, Decodable {
var UUIDpic: UUID = UUID()
var alpha: Int = 1
var buttonType: Int = 0
var buttonAction : [String: [ButtonAction]] = [:] //Dictionary I edited using the new struct
var buttonNameColor: String = ""
var buttonNameFontSize: Int = 10
var buttonShape: String = ""
var loggerRect: String = ""
var maskColor: String = ""
var name: String = ""
}
Below is the struct I added
struct ButtonAction: Codable {
var action: String
var array_linked_of_buttons: [[String:String]]
init(action: String, array_linked_of_buttons: [[String:String]]) {
self.action = action
self.array_linked_of_buttons = array_linked_of_buttons
}
}
Make sure to init your struct or it won't work.

Swift - Why is my JSON object element only adding the last array element?

I have a problem with my JSON object. Everything is working fine creating and printing out my JSON object, apart from the idQty part. It only prints the last key value result. I assume I have a problem with my for loop. If anybody can point out where I've went wrong, it would be of huge help.
Code below:
struct Order: Codable {
let idQty: [IdQty]
let collection: String
let name: String
let phone: Int
let doorNum: Int
let street: String
let postcode: String
}
struct IdQty: Codable {
let itemId: Int
let qty: Int
}
class CheckoutServer: NSObject, URLSessionDataDelegate {
var inputVals = [Int:Int]()
var idQty = [IdQty]()
var collection = String()
var name = String()
var phone = Int()
var doorNum = Int()
var street = String()
var postcode = String()
var request = URLRequest(url: NSURL(string: "http://192.168.1.100/api/AddOrder.php")! as URL)
func downloadItems() {
for(key,value) in inputVals {
idQty = [IdQty(itemId: key,qty: value)]
}
let order = Order(idQty: idQty,collection: collection,name: name,phone: phone,doorNum: doorNum,street: street,postcode: postcode)
let encodedOrder = try? JSONEncoder().encode(order)
var json: Any?
request.httpMethod = "POST"
if let data = encodedOrder {
json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
if let json = json {
}
}
let postParameters = "json="+String(describing: json!)
request.httpBody = postParameters.data(using: .utf8)
print(String(describing: json!))
let defaultSession = URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: request) { (data, response, error) in
if error != nil {
print("Failed to upload data at Menu Type Items")
} else {
print("Data uploaded")
}
}
task.resume()
}
}
Below is the output. the 'idQty' part only ever returns the last entry in the [Int:Int] dictionary:
{
collection = Delivery;
doorNum = 4;
idQty = (
{
itemId = 14;
qty = 2;
}
);
name = James;
phone = 4355345;
postcode = Test;
street = TestStreet;
}
You should append new value to your array instead of recreating it on each iteration
for(key,value) in inputVals
{
idQty.append(IdQty(itemId: key,qty: value))
}