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.
Related
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
I am very new to Swift coming from Obj C background. I am struggling to access a couple of key value pairs in a dictionary that comes from some JSON returned from an API endpoint. I can get as far as "data" but not sure how to get access to the arrays below that key. Specifically I am trying to return values for "id" and "price" as follows:
id = 1;
price = "20160.67609688202"
I would appreciate any help you might offer. Thank you.
Swift:
#IBAction func buttonAction(_ sender: Any) {
// Create the URL
let query = txt_Symbol.text
let api = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?CMC_PRO_API_KEY=myKey&symbol="
let endpoint = query!
let url = URL(string: api + endpoint)
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string:\n \(dataString)")
do {
if let convertedJsonIntoDict = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
// Print out entire dictionary
print("convertedJsonIntoDict:")
print(convertedJsonIntoDict)
// UI API called on a background thread
// Call this on the main thread
DispatchQueue.main.async {
//Do UI Code here.
self.txtarea_JSON.text = convertedJsonIntoDict.description
// Get a value by key
let coinData = convertedJsonIntoDict["data"]
print(coinData ?? "coin could not be read")
// how do I access these key value pairs?
// id = 1;
// price = "20160.67609688202"
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
task.resume()
}
JSON:
{
data = {
BTC = {
"circulating_supply" = 19138912;
"cmc_rank" = 1;
"date_added" = "2013-04-28T00:00:00.000Z";
id = 1;
"is_active" = 1;
"is_fiat" = 0;
"last_updated" = "2022-09-02T03:39:00.000Z";
"max_supply" = 21000000;
name = Bitcoin;
"num_market_pairs" = 9718;
platform = "<null>";
quote = {
USD = {
"fully_diluted_market_cap" = "423374198034.52";
"last_updated" = "2022-09-02T03:39:00.000Z";
"market_cap" = "385853405678.7285";
"market_cap_dominance" = "39.0616";
"percent_change_1h" = "0.83296442";
"percent_change_24h" = "0.16774456";
"percent_change_30d" = "-11.73989491";
"percent_change_60d" = "5.58985077";
"percent_change_7d" = "-6.59123025";
"percent_change_90d" = "-31.87091684";
price = "20160.67609688202";
tvl = "<null>";
"volume_24h" = "29753736862.94145";
"volume_change_24h" = "-7.2446";
};
};
"self_reported_circulating_supply" = "<null>";
"self_reported_market_cap" = "<null>";
slug = bitcoin;
symbol = BTC;
tags = (
mineable,
pow,
"sha-256",
"store-of-value",
"state-channel",
"coinbase-ventures-portfolio",
"three-arrows-capital-portfolio",
"polychain-capital-portfolio",
"binance-labs-portfolio",
"blockchain-capital-portfolio",
"boostvc-portfolio",
"cms-holdings-portfolio",
"dcg-portfolio",
"dragonfly-capital-portfolio",
"electric-capital-portfolio",
"fabric-ventures-portfolio",
"framework-ventures-portfolio",
"galaxy-digital-portfolio",
"huobi-capital-portfolio",
"alameda-research-portfolio",
"a16z-portfolio",
"1confirmation-portfolio",
"winklevoss-capital-portfolio",
"usv-portfolio",
"placeholder-ventures-portfolio",
"pantera-capital-portfolio",
"multicoin-capital-portfolio",
"paradigm-portfolio"
);
"total_supply" = 19138912;
"tvl_ratio" = "<null>";
};
};
status = {
"credit_count" = 1;
elapsed = 35;
"error_code" = 0;
"error_message" = "<null>";
notice = "<null>";
timestamp = "2022-09-02T03:41:38.218Z";
};
}
You could decode the api response into a set of models (struct) instead of a dictionary.
Here is an example code that shows how to decode the api response and use it.
(you will need to adjust the models according to the server documentations)
Once you have the data decoded into the models, it is far easier to deal with than a dictionary of nested (key,value) pairs.
let formatter: DateFormatter = {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return frmt
}()
if let data = data {
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let decoded = try decoder.decode(APIResponse.self, from: data)
// print("\n---> decoded: \n \(decoded)")
if let coinData = decoded.data.first?.value {
print("\n---> coinData: \n \(coinData)")
print("\n---> coinData name: \n \(coinData.name)")
print("\n---> coinData quote: \n \(coinData.quote)")
print("\n---> coinData quote usd: \n \(coinData.quote.usd)")
}
} catch {
print("==> decoding error: \(error)")
}
}
Where the models are declared as follows:
struct APIResponse: Codable {
let data: [String : CoinData]
let status: Status
}
struct CoinData: Identifiable, Codable {
let id: Int
let name, symbol, slug: String
let isActive, isFiat, circulatingSupply, totalSupply: Int
let maxSupply: Int
let dateAdded: Date
let numMarketPairs, cmcRank: Int
let lastUpdated: Date
let tags: [String]
let platform, selfReportedCirculatingSupply, selfReportedMarketCap: String?
let quote: Quote
enum CodingKeys: String, CodingKey {
case id, name, symbol, slug
case isActive = "is_active"
case isFiat = "is_fiat"
case circulatingSupply = "circulating_supply"
case totalSupply = "total_supply"
case maxSupply = "max_supply"
case dateAdded = "date_added"
case numMarketPairs = "num_market_pairs"
case cmcRank = "cmc_rank"
case lastUpdated = "last_updated"
case tags, platform
case selfReportedCirculatingSupply = "self_reported_circulating_supply"
case selfReportedMarketCap = "self_reported_market_cap"
case quote
}
}
struct Quote: Codable {
let usd: Usd
enum CodingKeys: String, CodingKey {
case usd = "USD"
}
}
struct Usd: Codable {
let price, volume24H, volumeChange24H, percentChange1H: Double
let percentChange24H, percentChange7D, percentChange30D, marketCap: Double
let marketCapDominance: Int
let fullyDilutedMarketCap: Double
let lastUpdated: Date
enum CodingKeys: String, CodingKey {
case price
case volume24H = "volume_24h"
case volumeChange24H = "volume_change_24h"
case percentChange1H = "percent_change_1h"
case percentChange24H = "percent_change_24h"
case percentChange7D = "percent_change_7d"
case percentChange30D = "percent_change_30d"
case marketCap = "market_cap"
case marketCapDominance = "market_cap_dominance"
case fullyDilutedMarketCap = "fully_diluted_market_cap"
case lastUpdated = "last_updated"
}
}
struct Status: Codable {
let timestamp: Date
let errorCode: Int
let errorMessage: String
let elapsed, creditCount: Int
enum CodingKeys: String, CodingKey {
case timestamp
case errorCode = "error_code"
case errorMessage = "error_message"
case elapsed
case creditCount = "credit_count"
}
}
From the JSON response you showed in your question here is the way to get values from it:
do {
guard let response = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
print("JSONSerialization Not possible")
return
}
guard let data = response["data"] as? [String: Any] else { return }
guard let btc = data["BTC"] as? [String: Any] else { return }
guard let quote = btc["quote"] as? [String: Any], let usd = quote["USD"] as? [String: Any] else { return }
let id = btc["id"] as? Int ?? 0
let price = usd["price"] as? String ?? ""
print(id) //1
print(price) //20160.67609688202
} catch let error {
print(error.localizedDescription)
}
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
}
}
}
}
What will be the best way to optimize this swift code to filter without lagging. I have tried both appending and binding but still, it lags.
import Contacts
import SwiftUI
import Combine
class ContactStore: ObservableObject {
#Published var contacts: [ContactsModel] = []
var searchText: String = ""{
didSet {
self.fetchContacts()
}
}
init() {
self.fetchContacts()
}
private func fetchContacts() {
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let error = error {
print("failed to request access", error)
return
}
if granted {
// print("access granted")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactBirthdayKey]
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey] as [CNKeyDescriptor]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
do {
if self.searchText.isEmpty {
try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointer) in
self.contacts.append(ContactsModel(firstName: contact.givenName, lastName: contact.familyName, birthDay: contact.birthday?.date ?? Date(timeInterval: -3600, since: Date()) , telephone: contact.phoneNumbers.first?.value.stringValue ?? ""))
})
} else {
self.contacts = []
let predicate = CNContact.predicateForContacts(matchingName: self.searchText)
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
print(contacts)
for i in contacts{
self.contacts = [ContactsModel(firstName: i.givenName, lastName: i.familyName, telephone: i.phoneNumbers.first?.value.stringValue ?? "")]
// self.contacts.append(ContactsModel(firstName: i.givenName, lastName: i.familyName, telephone: i.phoneNumbers.first?.value.stringValue ?? ""))
}
}
} catch let error {
print("Failed to enumerate contact", error)
}
} else {
print("access denied")
}
}
}
}
Try with searchText as a #Published var. Then use Combine to subscribe to, adding a debounce. In this way, you can limit the frequency of execution of the fetch request.
It would be something like this
$searchText
.debounce(for: 5, scheduler: RunLoop.main) //5 seconds debounce
.sink(
receiveValue: {
self.fetchContacts()
}
)
.store(in: &disposables)
I am using swift 4 xcode 9.2, I got the below error when using JSONDecoder.
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [],
debugDescription: "Expected to decode Array but found a dictionary instead.",
underlyingError: nil))
class Hits: Codable {
let hits: [Hit]
init(hits: [Hit]) {
self.hits = hits
}
}
class Hit: Codable {
let recipe: String
let uri: String
let label: String
let image: String
let source: String
let url: String
let shareAs: String
let yield: String
init(recipe: String, uri: String, label: String, image: String, source: String, url: String, shareAs: String, yield: String) {
self.recipe = recipe
self.uri = uri
self.label = label
self.image = image
self.source = source
self.url = url
self.shareAs = shareAs
self.yield = yield
}
}
func downloadJSON() {
guard let downloadURL = url else {return}
URLSession.shared.dataTask(with: downloadURL) { (data, urlResponse, error) in
guard let data = data, error == nil, urlResponse != nil else { print("Something is wrong"); return }
print("download completed")
do {
let decoder = JSONDecoder()
let foods = try decoder.decode([Hits].self, from: data)
print(foods)
} catch {
print(error)
}
}.resume()
}
Here is the JSON:
https://api.edamam.com/search?q=chicken&app_id=110d8671&app_key=3f01522208d512f592625dfcd163ff5c&from=0&to=10
The error clears states that you are trying to decode an array but the actual type is a dictionary (single object).
Replace
let foods = try decoder.decode([Hits].self, from: data)
with
let foods = try decoder.decode(Hits.self, from: data)
And your classes (actually structs are sufficient) are
struct Recipe : Decodable {
let uri : URL
let label : String
let image : URL
let source : String
let url : URL
let shareAs : URL
let yield : Double
let calories, totalWeight, totalTime : Double
}
struct Hits: Decodable {
let hits: [Hit]
}
struct Hit: Decodable {
let recipe: Recipe
}
hits = try decoder.decode(Hits.self from: data)