Store Indetifiable struct array in Firestore - arrays

I have this code:
struct Restaurants: Identifiable {
let name: String
let imageUrl: URL
let id: String
let rating: Double
let url: String
let category: String
static var viewModels: [Restaurants] = [
Restaurants(name: "Silverio's Mexican Kitchen", imageUrl: URL(string: "https://mainsite-prod-cdn.azureedge.net/partner-images/432257/micrositeimage_p1.jpg")!, id: "hjkhjhjh", rating: 2, url: "https://google.com"),//, category: "Mexican"),
Restaurants(name: "Taqueria La Esquinita", imageUrl: URL(string: "https://s3-media0.fl.yelpcdn.com/bphoto/x-KCQ7osmvBWLA9WpPdO_Q/o.jpg")!, id: "hjdha", rating: 3, url: "https://google.com")//, category: "Mexican")
] {
didSet {
print("data set")
}
}
}
How do I store viewModels in Firestore? I want to maintain this object structure so that I can carry it across multiple devices.
The idea of this app is to help groups find a place to eat. Therefore, I have this structure:
I am now using sub-collections for each Restaurant thanks to #FaridShumbar. How do I get documents from a sub-collection?

You need the get() method in order to retrieve documents.
Following examples from the documentation, you could retrieve your documents as follows:
db.collection("parties").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
This example retrieves all documents in a collection.

I did it by saving each element in the struct separately and then putting it back together when I get the document.
Save Code:
db.collection("parties").document(Utilities.code).collection("Restaurants").document(card.name).setData([
"name" : card.name,
"img" : imgUrl,
"id" : card.id,
"rating" : card.rating,
"url" : card.url,
"yes" : FieldValue.arrayUnion([Utilities.name])
], merge: true) { error in
if error != nil {
print("error adding restaurant: \(error!)")
}
}
Retrieve code:
class restaurantsFirebase: ObservableObject {
#Published var restaurants: [RestaurantListViewModel] = []
private let db = Firestore.firestore()
private var devices = Array<String>()
func fetchData() {
db.collection("parties").document(Utilities.code).addSnapshotListener { doc, error in
if error == nil {
if doc != nil && doc!.exists {
if let getDevices = doc!.get("devices") as? Array<String> {
self.devices = getDevices
}
}
}
}
db.collection("parties").document(Utilities.code).collection("Restaurants").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("no documents")
return
}
self.restaurants = documents.compactMap({ (queryDocumentSnapshot) in
print(queryDocumentSnapshot.data())
let data = queryDocumentSnapshot.data()
let name = data["name"] as? String ?? ""
let img = data["img"] as? String ?? ""
let id = data["id"] as? String ?? ""
let rating = data["rating"] as? Double ?? 0
let url = data["url"] as? String ?? ""
let yes = data["yes"] as? Array<String> ?? []
let compiled = RestaurantListViewModel(name: name, imageUrl: URL(string: img)!, id: id, rating: rating, url: url)
print("restaurant = \(compiled), restaurants = \(self.restaurants)")
print("yes = \(yes), devices = \(self.devices)")
if yes == self.devices {
// self.restaurants.append(compiled)
return compiled
}else {
return nil
}
})
}
}
}
Use:
#ObservedObject private var viewModels = restaurantsFirebase()
db.collection("parties").document(Utilities.code).addSnapshotListener { doc, error in
if error == nil {
if doc != nil && doc!.exists {
if let dev = doc!.get("devices") as? Array<String> {
print("devices = \(dev)")
devices = dev
}
if let done = doc!.get("devicesDone") as? Array<String> {
print("devicesDone = \(done), devices = \(devices)")
if done == devices {
self.viewModels.fetchData()
showFinal = true
results = false
}
}
}
}

Related

Push the data from the request into the array

I want to write the data that I received from the request into an array and then display it through a list
Here is my structure for the json file
struct DataRespons: Codable {
let data: [String]
let status: String
}
struct UserRespons: Codable {
let status: String
let data: UserData
}
struct UserData: Codable, Identifiable {
let id: String
let firstName: String
let lastName: String
let age: Int
let gender: String
let country: String
}
This is my class for JSON requests and decoding
import Foundation
#MainActor
class NetworkModel: ObservableObject {
#Published var listId: [String] = []
var statusList = ""
var statusUser = ""
var temp = ""
var user: [UserData] = [] // here I am not sure if this type Array
#Published var userData = UserRespons(status: "??", data: UserData(id: "???", firstName: "???", lastName: "??", age: 4, gender: "???", country: "???"))
this func for receive a letter with links to which I should make requests
func getList() {
guard let url = URL(string: "https://opn-interview-service.nn.r.appspot.com/list") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.addValue("bearer \(token)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Requst error",error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async { [self] in
do {
let decoded = try JSONDecoder().decode(DataRespons.self, from: data)
self.listId = decoded.data
self.statusList = decoded.status
for i in self.listId.indices {
print("This is id[\(i)] = \(listId[i])")
getUser(url: "\(listId[i])")
// #MARK: NEED HERE HELP user.append(.init(id: <#T##String#>, firstName: <#T##String#>, lastName: <#T##String#>, age: <#T##Int#>, gender: <#T##String#>, country: <#T##String#>))
}
} catch let error{
print("Error decode",error)
}
}
}
}
dataTask.resume()
}
I want to add data that will come from requests to an empty array so that it can then be displayed in the list
function for decoding data user
func getUser(url: String) {
guard let url = URL(string: "https://opn-interview-service.nn.r.appspot.com/get/\(url)") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.addValue("bearer \(token)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Requst error",error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async { [self] in
do {
let decoded = try JSONDecoder().decode(UserRespons.self, from: data)
self.userData = decoded
self.statusUser = decoded.status
print("UserData: name = \(userData.data.firstName) Lastname = \(userData.data.lastName) gender = \(userData.data.gender)")
} catch let error{
print("Error decode",error)
}
}
}
}
dataTask.resume()
}
I don't know how to throw all the data into the array
First of all iterating the indices is cumbersome, replace
for i in self.listId.indices {
print("This is id[\(i)] = \(listId[i])")
getUser(url: "\(listId[i])")
}
with
for id in self.listId {
print("This is \(id)")
getUser(url: id)
}
But this is not the right place to append the user data. Do it in getUser. But first declare the array as #Published (and in plural form)
#Published var users = [UserData]()
and delete the property userData because it's not needed.
#Published var userData = UserRespons(status: "??", data: UserData(id: "???", firstName: "???", lastName: "??", age: 4, gender: "???", country: "???"))
Now replace the decoding part in getUser with
let decoded = try JSONDecoder().decode(UserRespons.self, from: data)
self.statusUser = decoded.status
users.append(decoded.data)
print("UserData: name = \(decoded.data.firstName) Lastname = \(decoded.data.lastName) gender = \(decoded.data.gender)")
I recommend to move to async/await. It's much less code and it switches to the main thread automatically.

Get an [String] link from a json file

Here is my structure for the json file
struct DataRespons: Codable {
let data: [String]
let status: String
}
JSON file url
{
"status": "success",
"data": [
"f7c75c1f-10ab-4298-9dc9-e80b7bd07dfd",
"6f5f6eeb-191d-4ad9-b5ef-6f61fd5fcefc",
"8008800880088008",
"64a3f5d0-37c7-4c30-8d0f-3b67fb5c8fde"
]
}
This is my class for JSON requests and decoding
I use these functions to get an array of links
I hope I wrote correctly what I want to receive, and if there are any questions, please contact me
#MainActor
class NetworkModel: ObservableObject {
#Published var listId: [String] = []
var statusList = ""
var statusUser = ""
#Published var userData = UserRespons(status: "??", data: UserData(id: "???", firstName: "???", lastName: "??", age: 4, gender: "???", country: "???"))
func getList() {
guard let url = URL(string: "some URL") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.addValue("bearer \(token)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Requst error",error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async {
do {
let decoded = try JSONDecoder().decode(DataRespons.self, from: data)
self.listId = decoded.data
self.statusList = decoded.status
} catch let error{
print("Error decode",error)
}
}
}
}
dataTask.resume()
}
I can't through index [String] to get each element
use this code to access the data elements of an array:
// ....
self.listId = decoded.data
for i in listId.indices {
print("--> listId[\(i)] = \(listId[i]) ")
}
print("--> listId[0] = \(listId[0]) ")
print("--> listId[1] = \(listId[1]) ")
// ....
Read the very basics of Swift, specially regarding arrays here:
https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html

Swift: How to add more than one item to JSON array?

I am trying to create a basic to-do application for command-line in Swift. Below is my function to add a new item to the to-do array, but the new entry keeps overwriting the old one, instead of creating a new one. In the end, there is only one entry in the todo.json file.
When I multiply the entries and .append statements manually it works, but probably my brain is too dead to figure it out at the moment.
struct TodoItem: Codable {
let name: String
}
var todoList = [TodoItem]()
func addToList(_ item: String) -> String {
let todoItem = TodoItem(name: item)
todoList.append(todoItem)
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent("example")
.appendingPathExtension("json")
let encoder = JSONEncoder()
try encoder.encode(todoList).write(to: fileURL)
} catch {
return "Error: \(error.localizedDescription)"
}
return "Item added: \(todoItem.name)"
}
Your code works fine. I think the problem rests in the fact that todoList is empty when you run this. But you can write code to retrieve the contents of the JSON:
var todoList: [TodoItem] = []
func retrieveToDoList() {
guard let data = try? Data(contentsOf: fileURL) else { return }
todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}
So, for example, consider:
retrieveToDoList()
addToList("foo")
addToList("bar")
addToList("baz")
print(todoList)
// if you want to check the json
let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)
That results in:
[MyApp.TodoItem(name: "foo"), MyApp.TodoItem(name: "bar"), MyApp.TodoItem(name: "baz")]
[
{
"name" : "foo"
},
{
"name" : "bar"
},
{
"name" : "baz"
}
]
And if I later do:
addToList("abc")
addToList("def")
addToList("hij")
I then get:
[
{
"name" : "foo"
},
{
"name" : "bar"
},
{
"name" : "baz"
},
{
"name" : "abc"
},
{
"name" : "def"
},
{
"name" : "hij"
}
]
So, every time the app starts up, just make sure to call retrieveToDoList before trying to append items or else the toDoList will be empty.
FYI, this is the code I used to generate the above. Hopefully it illustrates the idea.
struct TodoItem: Codable {
let name: String
}
class ViewController: UIViewController {
private var todoList: [TodoItem] = []
private let fileURL = try! FileManager.default
.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent("example.json")
override func viewDidLoad() {
super.viewDidLoad()
retrieveToDoList()
addToList("foo")
addToList("bar")
addToList("baz")
print(todoList)
// if you want to check the json
let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)
}
}
private extension ViewController {
func retrieveToDoList() {
guard let data = try? Data(contentsOf: fileURL) else { return }
todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}
#discardableResult
func addToList(_ item: String) -> String {
let todoItem = TodoItem(name: item)
todoList.append(todoItem)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(todoList).write(to: fileURL)
} catch {
return "Error: \(error.localizedDescription)"
}
return "Item added: \(todoItem.name)"
}
}

How can I remove whole dictionary in nested array at specific index after filter some specific value

I have API response with nested array . But I can't understand how can I remove whole Dict by filtering the value .
This is the response screenshot
https://imgur.com/XIDyfYX
here is the json Resonse :- https://del.dog/lofavofogo.json
I have tried this but I don't know how to get filter nested value and remove whole dict at specific index
How to remove pairs from dictionary at specific index - Swift?
I want to remove the dict where section name are "NA"
Here is the code :-
Model Class For API Response :-
class filterclass: NSObject {
var classesID : String?
var classname : String?
var section = [filterSections]()
init(json: [String: Any]) {
if let classname = json["class"] as? String {
self.classname = classname
}
if let classesID = json["classesID"] as? String {
self.classesID = classesID
}
print("classname",classname)
if let evUserGoing = json["classsection"] as? [[String: Any]] {
if self.section.count > 0
{
self.section.removeAll()
}
for evUser in evUserGoing {
// print("evUser",evUser)
let userGoing = filterSections(json: evUser)
self.section.append(userGoing)
}
for sec in section {
let section = sec.secctionname
let setionID = sec.sectionID
}
}
}
}
class filterSections: NSObject {
var sectionID : String?
var secctionname : String?
var isSelctedSection : Bool = false
init(json: [String: Any]) {
if let sectionID = json["sectionID"] as? String {
self.sectionID = sectionID
}
if let secctionname = json["section"] as? String {
self.secctionname = secctionname
}
print("sectioname",secctionname)
}
}
API POST Method TO hit API :-
func getClassSectionAPI() {
if ReusableClass.sharedInstance.isNetworkAvailable() == true
{
ReusableClass.sharedInstance.showActivityIndicator()
let UUid = LoginUserInfo.sharedInstance.uuid!
let dictionary = ["uuid":UUid,"device_id":devicetoken,"school_id":LoginUserInfo.sharedInstance.schoolId!, "user_type":LoginUserInfo.sharedInstance.usertype!]
print(dictionary)
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(dictionary) {
if let jsonString = String(data: jsonData, encoding: .utf8) {
// print(jsonString)
let cipher:String = CryptoHelper.encrypt(input:jsonString)!;
let NewEncryption = "data=\(cipher)"
print(NewEncryption)
let hmac_md5 = cipher.hmac(algorithm: .sha512, key: kHMACKey)
print("hmac",hmac_md5)
UserDefaults.standard.set(hmac_md5, forKey: Headerkey)
Singleton.sharedInstance.getWebservicesverify(params: NewEncryption, Methodname: KFilterClassSection, data: Stringnil)
{ (result) in
DispatchQueue.main.async {
ReusableClass.sharedInstance.hideActivityIndicator()
}
if result != nil
{
do {
let jsonData = try JSONSerialization.data(withJSONObject: result)
if let json = String(data: jsonData, encoding: .utf8) {
let Dict = function.convertToDictionary(text: json)! as NSDictionary
guard let data = Dict[KData] as? String
else
{
return
}
self.baseDict = data
}
}
catch {
}
guard let output = CryptoHelper.decrypt(input:self.baseDict)
else
{
return
}
print(output)
let mainDict = function.convertToDictionary(text: output)! as NSDictionary
let status = mainDict[KStatus] as! NSInteger
if(status == 1)
{
DispatchQueue.main.async {
print("Main dict",mainDict)
guard let messageArray = mainDict["data"] as? [[String: Any]] else{
return
}
if self.arrayClasSection.count > 0
{
self.arrayClasSection.removeAll()
}
print("Main dict",messageArray)
for arr in messageArray {
let obj = filterclass.init(json: arr)
if let index = self.arryFilterTemperary.index(where: { $0.classname == obj.classname }) {
// let filtered = self.arryFilterTemperary.filter { $0.classname == "NA" }
obj.section = self.arryFilterTemperary[index].section
self.arrayClasSection.append(obj)
for sec in self.arryFilterTemperary[index].section {
let section = sec.sectionID
let sectionName = sec.secctionname
self.NASection = sec.secctionname!
print(self.NASection)
self.selectedNASectionID = sec.sectionID!
// let test = self.arryFilterTemperary[index].section.filter { !$0.value.contains("") }
// print(test)
}
}
else
{
self.arrayClasSection.append(obj)
}
}
ReusableClass.sharedInstance.hideActivityIndicator()
self.tableFilter.reloadData()
}
}
I want to append the data to array but before appending I want to
filter that "NA" value dict from the array
Since this is your first question I go to some greater length in answering it than usual. Playgrounds are an exceptional way to demonstrate your problem, so you should always try to compose your questions in a form of one. I will post my answer directly from the Playground I have done.
With that out of the way lets get to the question. Your main problem seems to be that you tried an ill fated JSONSerialization "shortcutt" route. This looks cheap from the outside, but working with the unavoidable optionality of a [String:Any] comes at a high cost in a language like Swift. The way to go is the brilliant Codable protocol, at least in my opinion. Once you define your data structure properly Xcode has so much more possibilities to guide you through the APIs that writing your filter code becomes a piece of cake.
Enough of the ranting, let's get to the pizza.
import UIKit
let dataStr = """
{
"status":1,
"message":"Class and sections list",
"data":[
{
"classsection":[
{
"section":"A",
"sectionID":"1",
"classesID":"1"
},
{
"section":"B",
"sectionID":"3",
"classesID":"1"
}
],
"class":"First",
"classesID":"1"
},
{
"classsection":[
{
"section":"A",
"sectionID":"2",
"classesID":"2"
},
{
"section":"B",
"sectionID":"7",
"classesID":"2"
}
],
"class":"Second",
"classesID":"2"
},
{
"classsection":[
{
"section":"A",
"sectionID":"20",
"classesID":"15"
}
],
"class":"Third",
"classesID":"15"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"33",
"classesID":"22"
}
],
"class":"Pre Nursery",
"classesID":"22"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"34",
"classesID":"23"
},
{
"section":"A",
"sectionID":"35",
"classesID":"23"
},
{
"section":"B",
"sectionID":"36",
"classesID":"23"
},
{
"section":"C",
"sectionID":"37",
"classesID":"23"
}
],
"class":"Fourth four",
"classesID":"23"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"38",
"classesID":"24"
}
],
"class":"Fifth",
"classesID":"24"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"39",
"classesID":"25"
}
],
"class":"sixth 6th",
"classesID":"25"
}
]
}
"""
struct Section: Codable {
let section, sectionId, classesId: String
enum CodingKeys: String, CodingKey {
case sectionId = "sectionID"
case classesId = "classesID"
case section
}
}
struct Class1: Codable {
let classsection: [Section]
let clazz, classesId: String
private enum CodingKeys: String, CodingKey {
case classsection
case clazz = "class"
case classesId = "classesID"
}
}
struct Response: Codable {
let status: Int
let message: String
let data: [Class1]
func filterSections(notMatching filterVal: String) -> Response {
let filteredData = data.map { (clazz) -> Class1 in
let filteredSections = clazz.classsection.filter { (sect) -> Bool in
sect.section != filterVal
}
return Class1(classsection: filteredSections, clazz: clazz.clazz, classesId: clazz.classesId)
}
return Response(status: status, message: message, data: filteredData)
}
}
let jsonData = dataStr.data(using:.utf8)!
do {
let res = try JSONDecoder().decode(Response.self, from: jsonData)
let filteredResponse = res.filterSections(notMatching: "NA")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(data:try encoder.encode(filteredResponse), encoding: .utf8)!)
} catch {
print(error)
}
As you can see your data structure is easily defined and the filtering code is really easy to understand once you wrap your head around the lambdas (which you should). The playground will output nicely formatted JSON as an answer, this way it is easy to check that your code does the right thing without all the messy parts of asynchronous communication (which are still nicely done in Swift).
Here's my last tipp of the day: Always try to isolate your problem as much as possible if you post a question on StackOverflow. I think your question carried too much legacy, you should whittle it down for the next one. This will improve your chances for a quick answer.

complex json data array in array in static tableview (for detail)

import UIKit
struct Base : Codable {
let genres : [Genres]?
let name : String?
let overview : String?
}
struct Genres : Codable {
let id : Int?
let name : String?
}
func fillSelectedShowDetails() {
let selectedURL = URL(string: " ")
guard let downloadedURL = selectedURL else {return}
URLSession.shared.dataTask(with: downloadedURL) { (data, urlResponse, error) in
guard let data = data, error == nil, urlResponse != nil else {
print("something went wrong in selectedURL")
return
}
print("Downloaded selectedURL")
do {
let decoder = JSONDecoder()
let decodedResults = try decoder.decode(Base.self, from: data)
self.detailedTV = decodedResults
DispatchQueue.main.async {
self.showNameLabel.text = decodedResults.name
self.overwievLabel.text = decodedResults.overview
if self.detailedTV?.genres?.count == 2 {
self.genreLabel.text = decodedResults.genres?[1].name
} else {
self.genreLabel.text = decodedResults.genres?[0].name
}
print(decodedResults.genres)
}
} catch {
print("something wrong after downloaded in selectedURL\(error)")
}
}.resume()
}
I need every genre name and write it to text. How can I do that?
If your goal is to create a single string from all of the values for the name property then use map and joined:
let genreNames = decodedResults.genres?.compactMap { $0.name }.joined(separator: ",") ?? """
self.genreLabel.text = genreNames

Resources