Good afternoon! I have a task, I need to do navigation (When I tap a cell, I navigate to another controller that contains the album list , then When I tap an album, I navigate to another controller that contains the list of images.
I do not know what the error is? Thank you ! My Json is http://appscorporation.ga/api-user/test
I created struct.
struct ProfileElement: Decodable {
let user: User
let postImage: String
let postLikes: Int
let postTags: String
}
struct User: Decodable {
let name, surname: String
let profilePic: String
let albums: [Album]
}
struct Album : Decodable {
let id: Int
let title: String
var images: [Image]
}
struct Image: Decodable {
let id: Int
let url: String
}
I decoded by this code
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode([ProfileElement].self, from: data)
I was advised to receive image data with
for item in result {
for album in item.user.albums {
for image in album.images {
print(image)
}
}
}
But unfortunately, I did not understand how I use data acquisition, then transfer and use them to other controllers.
Help me please.
My project in GitHub https://github.com/VladimirRebricov/TestProject
Related
I am new to swift . I am trying to convert json into model by using swift . I am using generic functions to complete the functions . Here is the structure of the json .
Here is the model I created based on jason .
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Codable {
let id, sol: Int
let camera: Camera
let imgSrc: String
let earthDate: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case id, sol, camera
case imgSrc = "img_src"
case earthDate = "earth_date"
case rover
}
}
// MARK: - Camera
struct Camera: Codable {
let id: Int
let name: String
let roverID: Int
let fullName: String
enum CodingKeys: String, CodingKey {
case id, name
case roverID = "rover_id"
case fullName = "full_name"
}
}
// MARK: - Rover
struct Rover: Codable {
let id: Int
let name, landingDate, launchDate, status: String
enum CodingKeys: String, CodingKey {
case id, name
case landingDate = "landing_date"
case launchDate = "launch_date"
case status
}
}
Here is the code in generic function.
func getModel<Model: Codable>(_ type: Model.Type, from url: String, completion: #escaping (Result<Model, NetworkError>) -> ()) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.other(error)))
return
}
if let data = data {
do {
let response = try JSONDecoder().decode(type, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.other(error)))
}
}
}
.resume()
}
I am trying to call this function form controller but it is showing the error Value of type 'Post' has no member 'data'
Here is the code to call the function.
class ViewModel {
private let networkManager = NetworkManager()
private var rovers = [Post]()
func getStories (){
networkManager
.getModel(Post.self, from: NetworkURLs.baseURL) {[weak self]result in
switch result{
case .success(let response):
self?.rovers = response.data.camera.map{$0.data} **// error on this line**
case .failure( let error):
print( error.localizedDescription)
}
}
}
Your response is of type Post which has no property data. You'll need to extract your photos array from the response, and then map across that array and retrieve the rovers property from it.
I think what you meant to write was
self?.rovers = response.photos.camera.map{$0.rover}
However even that won't work as your data structures don't match your JSON. From what can be seen, rover is a property on photo not on camera.
You will need to validate the JSON -> Model mapping
EDIT after JSON linked in comment below:
Using the JSON from the API, it confirms that camera and rover sit at the same level in the JSON:
{
"photos": [
{
"id": 102693,
"sol": 1000,
"camera": {
"id": 20,
"name": "FHAZ",
"rover_id": 5,
"full_name": "Front Hazard Avoidance Camera"
},
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FLB_486265257EDR_F0481570FHAZ00323M_.JPG",
"earth_date": "2015-05-30",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
},
....
So you will need to change your data model:
struct Photo : Codable{
let id : Int
let sol : Int
let camera : Camera
let imgSrc: String
let earthDate: String
let rover: Rover
}
and then to decode it
self?.rovers = response.photos.map{$0.rover}
nb. in Swift all struct types should be capitalised by convention.
Your struct of type Post does not have a member called "data", indeed.
You seem to be assuming, that your response object is of type Photo - but the error message is telling you, that it is of type Post, which only holds an array of Photo objects.
Try something like:
response.photos[0] to get the first Photo object out of the array - if there is one.
Then, assuming you got one response.photos[0].data gives you a Camera object already - you seem to be calling via the type, instead of the member name.
So in case you want to go one step further and access a Rover object, you need to do: response.photos[0].data.data
I see, that you want to extract several Rovers, supposedly one from each Post, but this will clash with your initial rovers variable being assigned a type of an array of Posts - this means you have to change it to [Rover]. I'm not sure if the map-function is actually suitable for what you want to do here.
Using a loop, iterating through the Posts and appending Rover objects to the Rover array would be the "manual" way to do it.
Hope this helps.
Edit: because you have edited your model mid-question, I can't see where "Post" has gone now. My reply might only fit the way the original question was posted.
I'm looking at this API for covid19 on RapidAPI. I Tested the endpoint and it showed this result:
[0:
"country":"Canada"
"provinces":[
0:{
"province":"Alberta"
"confirmed":754
"recovered":0
"deaths":9
"active":0
}...etc]
"latitude":56.130366
"longitude":-106.346771
"date":"2020-04-01"
}
]
Pretty straight forward. I want to parse the "provinces" segment, so in xcode I set up a couple models like this:
struct Country: Codable{
let country: String
let provinces: [Province]
}
struct Province: Codable{
let province: String
let confirmed: Int
let recovered: Int
let deaths: Int
let active: Int
}
I believe this is correct, but it won't parse. It only works when I comment out this bit:
struct Country: Codable{
let country: String
//let provinces: [Province]
}
meaning I can print out the name of the country, but only when the provinces object is commented out. This makes me think that there is something wrong with my model. What am I doing wrong here? I've looked up other examples and this should be working... I'm pretty sure.
EDIT: I'll add a bit more code to make it clearer what I'm doing:
override func viewDidLoad() {
super.viewDidLoad()
Service.shared.getInfo(requestURL: "url", host: "host", key: "12345", needsKey: true) { data in
if let data = data{
if let p = try? JSONDecoder().decode([Country].self, from: data){
//get the data and set it to a string
let provinceName: String = p[0].provinces[0].province
self.provinceStr = provinceName
}
DispatchQueue.main.async {
[unowned self] in
//print the string
print(self.provinceStr)
}
}
}
}
from the printout, this is the data structure you should be using:
struct Country: Codable {
let country, code: String
let confirmed, recovered, critical, deaths: Int
let latitude, longitude: Double
let lastChange, lastUpdate: Date // <-- or String
}
so I use I am trying to parse through this data:
{"status":"success","data":{"city":"Sunnyvale","state":"California","country":"USA","location":{"type":"Point","coordinates":[-122.03635,37.36883]},"current":{"weather":{"ts":"2020-07-23T00:00:00.000Z","tp":25,"pr":1009,"hu":44,"ws":6.2,"wd":330,"ic":"02d"},"pollution":{"ts":"2020-07-23T00:00:00.000Z","aqius":7,"mainus":"p2","aqicn":2,"maincn":"p2"}}}}
I am trying to get a hold of the aqius result, as well as the tp...
Here is my code right now, I have created these structs:
struct Response: Codable{
let data: MyResult
let status: String
}
struct MyResult: Codable {
let city: String
}
As you can see, I have gotten city, and I can confirm it works because when I get the request and print(json.data.city) it prints "Sunnyvale".
But how would I get the other values? I have been stuck on how to obtain values within the location , current and pollution data structures, how would I do this?
Thanks
There are tools that automatically generate Codable models from json string like: https://app.quicktype.io)
So your base struct models looks like below;
// MARK: - Response
struct Response: Codable {
let status: String
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let city, state, country: String
let location: Location
let current: Current
}
// MARK: - Current
struct Current: Codable {
let weather: Weather
let pollution: Pollution
}
// MARK: - Pollution
struct Pollution: Codable {
let ts: String
let aqius: Int
let mainus: String
let aqicn: Int
let maincn: String
}
// MARK: - Weather
struct Weather: Codable {
let ts: String
let tp, pr, hu: Int
let ws: Double
let wd: Int
let ic: String
}
// MARK: - Location
struct Location: Codable {
let type: String
let coordinates: [Double]
}
Decode;
let jsonData = jsonString.data(using: .utf8)!
let model = try? JSONDecoder().decode(Response.self, from: jsonData)
I'm trying to fetch data from a public API. However, all the data I need is accessible only by calling multiple URLs.
However, each JSON provided have a station_id and I'm trying to combine the data based on this value.
I am not sure which strategy I should use to "merge" the results (see code below)
I tried calling both URL at the same time.
Also tried to add the data from the second URL after calling the first URL.
first URL (https://api-core.bixi.com/gbfs/es/station_information.json)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"external_id":"0b100854-08f3-11e7-a1cb-3863bb33a4e4",
"name":"de la Commune / Place Jacques-Cartier",
"short_name":"6026",
"lat":45.50761009451047,
"lon":-73.55183601379395,
"capacity":89,}]
// ...
Second URL (https://api-core.bixi.com/gbfs/en/station_status.json)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"num_bikes_available": 39,
"num_docks_available":50,}]
// ...
Excepted Result (This is the structure I am looking for, not the final code)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"external_id":"0b100854-08f3-11e7-a1cb-3863bb33a4e4",
"name":"de la Commune / Place Jacques-Cartier",
"short_name":"6026",
"lat":45.50761009451047,
"lon":-73.55183601379395,
"capacity":89,
"num_bikes_available": 39,
"num_docks_available":50}]
//...
Structure I tried to pass the data in
struct BixiApiDataModel: Codable {
let last_updated: Int
let ttl: Int
let data: Stations
}
struct Stations: Codable {
let stations: [Station]
}
struct Station: Codable {
let station_id: String
let num_bikes_available: Int
let num_docks_available: Int
let external_id: String
let name: String
let short_name: String
let lat: Float
let lon: Float
let capacity: Int
}
Calling the URL
class Webservice {
func loadBixiApiDataModel(url: URL, completion: #escaping ([Station]?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let response = try? JSONDecoder().decode(BixiApiDataModel.self, from: data)
if let response = response {
DispatchQueue.main.async {
completion(response.data.stations)
}
}
}.resume()
}
}
I'm trying to display the combined information of a station. I assume the data I fetch after calling the first URL isn't stored when I call the second URL.
Should I call both APIs separately, store the data and then combine everything using the station_id value?
Or is it possible to call each APIs and append the data from the second URL based on the station_id?
Thanks in advance for your help.
I would do it like this
Handle each download separately
Keep the resulting data in separate structs
Merge them into a third struct and then use that third struct internally in the app
Handle each download separately
Download the station information first and store it in a dictionary with station_id as key and then download station status and use the same id to match the downloaded elements
Keep the resulting data in separate structs
Since the content of the downloaded data is quite different between the to API calls I would use two different structs for them, StationInformation and StationStatus. Looking at the type of data you might actually want to download status more often than information which seems to be more static so that is another reason to keep them separate.
Merge them into a third struct...
I would create a third struct that contains information from the two other structs, either as just two properties (shown below) or with properties that are extracted from the others
Here is an example of how the third struct could be implemented
struct Station {
let information: StationInformation
var status: StationStatus?
init(information: StationInformation) {
self.information = information
}
var id: String {
return information.stationId
}
mutating func merge(status: StationStatus) {
guard self.id == status.stationId else { return }
self.status = status
}
}
The download function could be modified to be generic to simplify the code. First the structs need to be modified
struct BixiApiDataModel<T: Decodable>: Decodable {
let data: Stations<T>
}
struct Stations<T: Decodable>: Decodable {
let stations: [T]
}
struct StationInformation: Codable {
let stationId: String
let externalId: String
//... rest of properties
}
struct StationStatus: Codable {
let stationId: String
let numBikesAvailable: Int
let numDocksAvailable: Int
}
then the function signature needs to be changed to
func loadBixiApiDataModel<T: Decodable>(url: URL, completion: #escaping ([T]?) -> ()) {
and the decoding needs to be changed (notice the improved error handling, never use try?)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(BixiApiDataModel<T>.self, from: data)
completion(response.data.stations)
} catch {
print(error)
}
And a simplified example of calling the function (but without any merging)
var informationArray: [StationInformation] = []
var statusArray: [StationStatus] = []
if let url = URL(string: "https://api-core.bixi.com/gbfs/es/station_information.json") {
loadBixiApiDataModel(url: url, completion: {(arr: [StationInformation]?) in
if let arr = arr { informationArray = arr }
print(informationArray.count)
})
} else { print("Not a valid url")}
if let url = URL(string: "https://api-core.bixi.com/gbfs/en/station_status.json") {
loadBixiApiDataModel(url: url, completion: {(arr: [StationStatus]?) in
if let arr = arr { statusArray = arr }
print(statusArray.count)
})
} else { print("Not a valid url")}
I'm fetching data from coinDesk API to get bitcoin rate related to other currencies, I've created 3 structs to save this data, but it's not possible to loop through the struct to know how many items I have there...
that's my structure:
struct Response: Codable {
var bpi: currencies
}
struct currencies: Codable {
var USD: info
var GBP: info
var EUR: info
}
struct info: Codable {
var code: String
var symbol: String
var description: String
var rate_float: Float
}
To save the data from API I just use:
let jsonData = try JSONDecoder().decode(Response.self, from: data)
It saves the data with no error but, when I try to loop through this data to populate tableViewCells it doesn't work.
what I'm doing know is...
let euro = jsonData.bpi.EUR
let dollar = jsonData.bpi.USD
let gbp = jsonData.bpi.GBP
let infos = [euro,dollar,gbp]
completion(infos)
This is sending the data to my UITableView and populating, but what if I had 500 currencies? it would not be practical at all.. how could I do this in a more effective way?
Thank you in advance for the answers.
Don't put keys instead
struct Response: Codable {
let bpi: [String:Info]
}
struct Info: Codable {
let code: String
let symbol: String
let description: String
let rate_float: Float
}
Then
let jsonData = try JSONDecoder().decode(Response.self, from: data)
print(jsonData.bpi["USD"])
so for all keys
let keys = Array(jsonData.bpi.keys)
let values = Array(jsonData.bpi.values)