How to save nested data to the Firestore? - swift

I'm new here. An error occurs while writing the nested data to the Firestore. This is my data structure:
struct CartArray: Codable {
var num:Int
var name:String
var price:Double
init (num: Int,name: String,price: Double)
{ self.num = num
self.name = name
self.price = price
}
this is the data recording function:
let db = Firestore.firestore()
var arrayCart: [CartArray] = []
#IBAction func buttonCheckout(_ sender: Any) {
saveData()
}
...
func saveData () {
let menuItems = [arrayCart]
var list_menuItem = [Any]()
for item in menuItems {
do {
let jsonData = try JSONEncoder().encode(item)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
list_menuItem.append(jsonObject)
}
catch {
// handle error
}
}
print(list_menuItem)
let parameters = [
"address": userAddress as Any,
"datetime": Timestamp(date: Date()),
"status": "Заказано",
"user_phone": userPhone as Any,
"username": userName as Any,
"total":cart.total,
"order":list_menuItem
]
db.collection("orders").document().setData(parameters)
{ err in
if let e = err {
print("$-- error save data \(e)")
} else {
print("success!")
}
}
}
this is the converting json of array:
[<__NSArrayI 0x600001f97880>( { name = "\U041f\U0438\U0440\U043e\U0436\U043a\U0438 \U0441 \U043c\U044f\U0441\U043e\U043c"; num = 3; price = 40; },
{ name = "\U041f\U0438\U0446\U0446\U0430 \U0421\U0442\U0430\U043d\U0434\U0430\U0440\U0442"; num = 1; price = 500; } ) ]
When saving occurs in the 'order' field, an 'Nested arrays are not supported' error occurs. Why?

The problem is that in the saveData() you are creating an array of arrays of CartArray.
Replace: let menuItems = [arrayCart]
with: let menuItems = arrayCart

Related

How to populate loaded records from firebase?

I wrote the function to lad the records from firebase but there's an error
Escaping closure captures mutating 'self' parameter
The function is written as follows:
let db = Firestore.firestore()
#State var libraryImages: [LibraryImage] = []
mutating func loadImages() {
libraryImages = []
db.collection(K.FStore.CollectionImages.collectionName).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for document in snapshotDocuments {
let documentData = document.data()
let title: String = documentData[K.FStore.CollectionImages.title] as! String
let thumbnailUrl: String = documentData[K.FStore.CollectionImages.thumbnailUrl] as! String
let svgUrl: String = documentData[K.FStore.CollectionImages.svgUrl] as! String
let libraryImageItem = LibraryImage(title: title, thumbnailUrl: thumbnailUrl, svgUrl: svgUrl)
self.libraryImages.append(libraryImageItem)
}
}
}
}
}
Does anyone know what is causing an error and how to get rid of it?
Move all this into reference type view model and use it as observed object in your view
Here is a demo of possible approach:
struct DemoView: View {
#ObservedObject var vm = ImagesViewModel()
// #StateObject var vm = ImagesViewModel() // << SwiftUI 2.0
var body: some View {
Text("Loaded images: \(vm.libraryImages.count)")
.onAppear {
self.vm.loadImages()
}
}
}
class ImagesViewModel: ObservableObject {
let db = Firestore.firestore()
#Published var libraryImages: [LibraryImage] = []
func loadImages() {
libraryImages = []
db.collection(K.FStore.CollectionImages.collectionName).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
var images = [LibraryImage]()
for document in snapshotDocuments {
let documentData = document.data()
let title: String = documentData[K.FStore.CollectionImages.title] as! String
let thumbnailUrl: String = documentData[K.FStore.CollectionImages.thumbnailUrl] as! String
let svgUrl: String = documentData[K.FStore.CollectionImages.svgUrl] as! String
let libraryImageItem = LibraryImage(title: title, thumbnailUrl: thumbnailUrl, svgUrl: svgUrl)
images.append(libraryImageItem)
}
DispatchQueue.main.async {
self.libraryImages = images
}
}
}
}
}
}

How to map the nested data in a document from Firestore by Swift?

I have a document data structure on Firestore like this:
pic1
pic2
So there are 2 map-objects inside the document and a collection and a another document inside this document
Then I create 3 model swift files for this document
task:
struct task {
var Name: String
var Address: String
var Car: CarModel
car Price: PriceModel
var dictionary: [String:Any] {
return [
"Name" : Name,
"Address" : Address,
"Car" : CarModel,
"Price" : PriceModel
]
}
init?(data: [String:Any]) {
guard let Name = dictionary["Name"] as? String,
let Address = dictionary["Address"] as? String,
let Car = ditionary["car"] as? CarModel,
let Price = dictionary["price"] as? PriceModel else{
return nil
}
self.Name = Name
self.Address = Address
self.Car = Car
self.Price = Price
}
}
CarModel:
struct CarModel {
var brand: String
var model: String
var year: String
var dictionary: [String:Any] {
return [
"brand" : brand,
"model" : model,
"year" : year,
]
}
init?(data: [String:Any]) {
guard let brand = dictionary["brand"] as? String,
let model = dictionary["model"] as? String,
let year = ditionary["year"] as? String else{
return nil
}
self.brand = brand
self.model = model
self.year = year
}
}
PriceModel:
struct PriceModel {
var basic: Int
var extra: Int
var dictionary: [String:Any] {
return [
"basic" : basic,
"extra" : extra,
]
}
init?(data: [String:Any]) {
guard let basic = dictionary["basic"] as? Int,
let extra = ditionary["extra"] as? Int else{
return nil
}
self.basic = basic
self.extra = extra
}
}
Then download the data with this following code:
func loadDataFromFirestore(completion: #escaping (Bool) -> ()) {
var success: Bool = false
DispatchQueue.global(qos: .userInteractive).async {
let downloadGroup = DispatchGroup()
let colRef = db.collection("tasks")
downloadGroup.enter()
colRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error: \(error)")
return
}
for document in querySnapshot!.documents{
let result = document.data()
print (result)
if let data = task(data: result){
print(data)
}
}
success = true
downloadGroup.leave()
}
downloadGroup.wait()
DispatchQueue.main.async {
completion(success)
}
}
}
I can get the data with comment the CarModel and PriceModel, but if I uncomment these two, it will let my app crash!
So how could I get the map-object to adapt to my code?
And the second question is: How can I get the document inside a document's collection with this kind of code?

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

How to query data from FireStore?

I try to query data from Firestore by using a map. My issue is I can't get only one field from the firestore.
The below code is structure of my data model.
UserData.swift
import Foundation
import FirebaseFirestore
struct UsersData {
var BOOKS_Field:Int
var NAME_FIELD:String
var PHOTO_FIELD:String
var USERLEVEL_FIELD:Int
var dictionary:[String:Any] {
return [
"BOOKS_Field":BOOKS_Field,
"NAME_FIELD":NAME_FIELD,
"PHOTO_FIELD":PHOTO_FIELD,
"USERLEVEL_FIELD":USERLEVEL_FIELD
]
}
}
extension UsersData : DocumentSerializable {
init?(dictionary: [String: Any]) {
guard let BOOKS_Field = dictionary["BOOKS_Field"] as? Int,
let NAME_FIELD = dictionary["NAME_FIELD"] as? String,
let PHOTO_FIELD = dictionary["PHOTO_FIELD"] as? String,
let USERLEVEL_FIELD = dictionary["USERLEVEL_FIELD"] as? Int
else {return nil}
self.init(BOOKS_Field: BOOKS_Field, NAME_FIELD: NAME_FIELD, PHOTO_FIELD:PHOTO_FIELD, USERLEVEL_FIELD:USERLEVEL_FIELD)
}
}
This is the place where I try to get users from the Firestorm server.
func getLeadersfromServer() {
let leaderRef = db.collection("USERS_Collection").order(by: "BOOKS_Field", descending: true).limit(to: 5)
leaderRef.addSnapshotListener { (snapShot, error) in
if let error = error {
print(error)
} else {
for document in (snapShot?.documents)! {
let data = document.data()
let docs = (data.compactMap({_ in UsersData(dictionary: data)}))
let user1 = docs[0]
print(user1.NAME_FIELD)
}
}
}
}
You can find the print output result below:
Name1 H.
Name2 B.
How can I get only one name by order?

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))
}