Initialization and Storage Error of API Data - swiftui

I have an exchange rate API initialization / storage problem. I read in some currency exchange rates and would like to store the data temporally in moneyRates then move the data to rateArray as ordered data. I am getting the error "No exact matches in call to initializer". The error is occurring at the line that begins "let result = try JSONSerialization...". I am also seeing a message in the sidebar (Xcode gray !) "/Foundation.Data:29:23: Candidate requires that the types 'MoneyRates' and 'UInt8' be equivalent (requirement specified as 'S.Element' == 'UInt8')". I'm guessing that I need to initialize moneyRates with some kind of format info.
I would like some explanation of the moneyRates error and how to resolve it. I'm not concerned with rateArray at this point. Thanks for your assistance.
struct MoneyRates {
let date: String
let base: String
let rates: [String: Double]
}
class CurrencyRates: ObservableObject{
#Published var moneyRates: [MoneyRates]
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
} else {
rateArray = [Double] (repeating: 0.0, count: 170)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
// retrieve exchange rates for all 150+ countries from internet and save to rateArray
func updateRates(baseCur: String) {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let result = try JSONSerialization.jsonObject(with: Data(moneyRates)) as! [String:Any] // <-- error is occurring here
var keys = Array(arrayLiteral: result.keys)
if let dateIndex = keys.firstIndex(of: "date"),
let date = result[keys[dateIndex]] as? String, keys.count == 2 {
keys.remove(at: dateIndex)
let base = keys.first!
let rates = MoneyRates(date: date, base: base, rates: result[base] as! [String:Double])
print(rates)
}
} catch {
print(error)
}
}
}.resume()
}
}

If you're trying to decode the result that you get from the URLSession, then instead of passing Data(moneyRates) to decode, you should be passing data from the dataTask closure:
let result = try JSONSerialization.jsonObject(with: data) as! [String:Any]

Related

Using Structure before Initialization

I am having initialization trouble with an exchange rate structure. In the method getRates I have been trying to implement dictionary key / value logic to copy exchange rates into an ordered array. I am getting the error "Variable 'moneyRates' used before being initialized". I tried adding a memberwise initializer but was unsure how to initialize the rate array. I have also been wondering if I should move the instance of MoneyRates to the top of the class instead of in the getRates method.
var currCode: [String] = ["usd", "afn", "all", "dzd", "amd", "ang", "aoa", "ars", "aud", "awg", "azn",
/* b */ "bsd", "bhd", "bdt", "bbd", "byr", "bzd", "bmd", "btn", "bob", "bam", "bwp", "brl", "bnd", "bgn", "bif",
/* c */ "cad", "khr", "cve", "xcd", "kyd", "xaf", "xof", "xpf", "clf", "clp", "cnh", "cny", "cop", "kmf", "cdf", "crc", "hrk", "cup", "czk"]
struct MoneyRates {
let date: String
let base: String
let rates: [String: Double]
}
class CurrencyRates: ObservableObject {
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
} else {
rateArray = [Double] (repeating: 0.0, count: 170)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
// retrieve exchange rates for all 150+ countries from internet and save to rateArray
func updateRates(baseCur: String) {
print("doing update")
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let result = try JSONSerialization.jsonObject(with: data) as! [String:Any]
var keys = Array(result.keys)
if let dateIndex = keys.firstIndex(of: "date"),
let date = result[keys[dateIndex]] as? String, keys.count == 2 {
keys.remove(at: dateIndex)
let base = keys.first!
print("base = \(base)")
print("date = \(date)")
let rates = MoneyRates(date: date, base: base, rates: result[base] as! [String:Double])
print(rates)
self.getRates(rates: rates, baseCur: baseCur)
}
} catch {
print(error)
}
}
}.resume()
}
// copy rates from MoneyRates to rateArray
func getRates (rates: MoneyRates, baseCur: String) -> () {
var moneyRates: MoneyRates
var currencyCode: String = ""
// loop through all available currencies
// the rates are stored in the same order as currCode
for index in 0..<currCode.count {
currencyCode = currCode[index]
// special handling for base currency
if currencyCode == baseCur {
rateArray[index] = 1.0000
} else {
print(currencyCode)
if moneyRates.rates[currencyCode] != nil { // error here
let unwrapped = (moneyRates.rates[currencyCode]!) // and here (same)
print( unwrapped)
rateArray[index] = 1.0 / unwrapped // want inverse exchange rate
}
}
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
}
The error you are getting is because you declare the variable "moneyRates"
but you do not instantiate it to something.
var moneyRates: MoneyRates
In other words there is nothing in "moneyRates",
but you are trying to get something from it in:
if moneyRates.rates[currencyCode] != nil {
...
}
So populate "moneyRates" with some data.

Exchange Rate Key Value Lookup With Weird JSON File Format

I need help with currency exchange rate lookup given a key (3 digit currency code). The JSON object is rather unusual with no lablels such as date, timestamp, success, or rate. The first string value is the base or home currency. In the example below it is "usd" (US dollars).
I would like to cycle through all the currencies to get each exchange rate by giving its 3 digit currency code and storing it in an ordered array.
{
"usd": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
"ang": 2.159569,
"aoa": 791.552347,
"ars": 111.887966,
"aud": 1.558363,
"awg": 2.164862,
"azn": 2.045728,
"bam": 1.9541,
"bbd": 2.429065,
"bch": 0.001278
}
}
In a slightly different formatted JSON object I used the following loop to copy exchange rates to an ordered array.
for index in 0..<userData.rateArray.count {
currencyCode = currCode[index]
if let unwrapped = results.rates[currencyCode] {
userData.rateArray[index] = 1.0 / unwrapped
}
}
The follow code is the API used to get the 3 digit currency codes and the exchange rates (called via UpdateRates).
class GetCurrency: Codable {
let id = UUID()
var getCurrencies: [String : [String: Double]] = [:]
required public init(from decoder: Decoder) throws {
do{
print(#function)
let baseContainer = try decoder.singleValueContainer()
let base = try baseContainer.decode([String : [String: Double]].self)
for key in base.keys{
getCurrencies[key] = base[key]
}
}catch{
print(error)
throw error
}
}
}
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var selectedBaseCurrency: String = "usd"
func UpdateRates() {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let baseCur = selectedBaseCurrency // usd, eur, cad, etc
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do{
let decodedResponse = try JSONDecoder().decode(GetCurrency.self, from: data)
DispatchQueue.main.async {
self.results = decodedResponse
// this prints out the complete table of currency code and exchange rates
print(self.results?.getCurrencies["usd"] ?? 0.0)
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
Thanks lorem ipsum for your help. Below is the updated ASI logic that copies the exchange rates to the rateArray using key/value lookups.
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
}else {
rateArray = [Double] (repeating: 0.0, count: 160)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
func updateRates(baseCur: String) {
...
DispatchQueue.main.async {
self.results = decodedResponse
// loop through all available currencies
for index in 0..<currCode.count {
currencyCode = currCode[index]
// spacial handling for base currency
if currencyCode == baseCur {
self.rateArray[index] = 1.0000
} else {
let homeRate = self.results?.getCurrencies[baseCur]
// complement and save the exchange rate
if let unwrapped = homeRate?[currencyCode] {
self.rateArray[index] = 1.0 / unwrapped
}
}
}
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}

How to get each data from JSON on Swift 3?

I'm registering some data of user in database and after that the API returns others data in JSON usuario, like this:
And i'm trying to get idUsuario, nome and cpf from this JSON and print to see if they are correct, but they don't appear on console!
#IBAction func botaoSalvar(_ sender: Any) {
let nomeUsuario = self.campoUsuario.text;
let cpf = self.campoCPF.text;
let senha = self.campoSenha.text;
let parameters = ["nome": nomeUsuario, "cpf": cpf, "senha": senha, "method": "app-set-usuario"]
let urlPost = "http://easypasse.com.br/gestao/wsCadastrar.php"
guard let url = URL(string: urlPost) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return }
request.httpBody = httpBody
let session = URLSession.shared
session.dataTask(with: request) {
(data, response, error) in
if let data = data {
do {
let dadosJson = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let usuario = json["usuario"] as? [String: Any] {
let idUsuario = usuario["idUsuario"] as? Int
let nome = usuario["nome"] as? String
let cpf = usuario["cpf"] as? Int
print(idUsuario as! Int, nome as! String, cpf as! Int)
}
} catch {
print(error)
}
}
}.resume()
The value for key usuario is an array, please notice the (, dictionary is {. Blame the owner of the service for singular / plural confusion 😉.
This is your code with a few swiftifications (native collection types and no never .mutableContainers):
if let data = data {
do {
if let dadosJson = try JSONSerialization.jsonObject(with: data) as? [String:Any],
let usuarios = dadosJson["usuario"] as? [[String:Any]] {
for usuario in usuarios {
if let nomeUsuario = usuario["nome"] as? String {
print(nomeUsuario)
}
if let idUsuario = usuario["idUsuario"] as? Int { // can also be `String`
print(idUsuario)
}
if let cpf = usuario["cpf"] as? Int { // can also be `String`
print(cpf)
}
}
}
} catch {
print(error)
}
}

How to parse alamofire json response in swift 3?

i am new to swift programming, i have spent considerable amount of time figuring out how to parse json response from alamofire server call. My Json response is
{"customer_info":[{"customer_id":"147","response_code":1}]}
and i want to access both variables. My swift code is
Alamofire.request(
URL_USER_REGISTER,
method: .post,
parameters: parameters,
encoding: JSONEncoding.default).responseJSON
{
if let json = response.result.value {
print (json)
}
if let result = response.result.value as? [String:Any] {
var names = [String]()
do {
if let data = data,
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let blogs = json["customer_info"] as? [[String: Any]] {
for blog in blogs {
if let name = blog["customer_id"] as? String {
names.append(name)
}
}
}
} catch {
print("Error deserializing JSON: \(error)")
}
print(names)
}
}
please help
Your code is parsing correctly. Add the following code to your blog loop and get the second variable out
if let response_code = blog["response_code"] as? Int {
//Do something here
}
So the complete code you are looking for is
let str = "{\"customer_info\":[{\"customer_id\":\"147\",\"response_code\":1}]}"
let data = str.data(using: .utf8)
do {
if let data = data,
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let blogs = json["customer_info"] as? [[String: Any]] {
for blog in blogs {
if let name = blog["customer_id"] as? String {
print(name)
}
if let response_code = blog["response_code"] as? Int {
print(response_code)
}
}
}
} catch {
print("Error deserializing JSON: \(error)")
}
i have modified the code and getting result now
if let jsonDict = response.result.value as? [String:Any],
let dataArray = jsonDict["customer_info"] as? [[String:Any]]
{
let nameArray = dataArray.flatMap { $0["customer_id"] as? String }
let nameArray2 = dataArray.flatMap { $0["response_code"] as? Int }
if(dataArray.count>0)
{
//store return customer id and response code
let customer_id_received = nameArray[0]
let response_code_received = nameArray2[0]
if(response_code_received==1)
{
//proceed with storing customer id in global variable
print(nameArray2[0])
}

api youtube in swift 3 error 403

I am trying to use youtube api in ios swift, and following this tutorial
http://www.appcoda.com/youtube-api-ios-tutorial/
HTTP Status Code = 403
Error while loading channel details: nil
I'm using swift 3
var urlString = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=\(textField.text)&type=\(type)&key=\(apiKey)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
// Create a NSURL object based on the above string.
let targetURL = URL(string: urlString)
// Get the results.
performGetRequest(targetURL, completion: { (data, HTTPStatusCode, error) -> Void in
if HTTPStatusCode == 200 && error == nil {
// Convert the JSON data to a dictionary object.
do {
let resultsDict = try JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<String, AnyObject>
// Get all search result items ("items" array).
let items: Array<Dictionary<String, AnyObject>> = resultsDict["items"] as! Array<Dictionary<String, AnyObject>>
// Loop through all search results and keep just the necessary data.
for i in 0 ..< items.count {
let snippetDict = items[i]["snippet"] as! Dictionary<String, AnyObject>
// Gather the proper data depending on whether we're searching for channels or for videos.
if self.segDisplayedContent.selectedSegmentIndex == 0 {
// Keep the channel ID.
self.desiredChannelsArray.append(snippetDict["channelId"] as! String)
}
else {
// Create a new dictionary to store the video details.
var videoDetailsDict = Dictionary<String, AnyObject>()
videoDetailsDict["title"] = snippetDict["title"]
videoDetailsDict["thumbnail"] = ((snippetDict["thumbnails"] as! Dictionary<String, AnyObject>)["default"] as! Dictionary<String, AnyObject>)["url"]
videoDetailsDict["videoID"] = (items[i]["id"] as! Dictionary<String, AnyObject>)["videoId"]
// Append the desiredPlaylistItemDataDict dictionary to the videos array.
self.videosArray.append(videoDetailsDict)
// Reload the tableview.
self.tblVideos.reloadData()
}
}
} catch {
print(error)
}
// Call the getChannelDetails(…) function to fetch the channels.
if self.segDisplayedContent.selectedSegmentIndex == 0 {
self.getChannelDetails(true)
}
}
else {
print("HTTP Status Code = \(HTTPStatusCode)")
print("Error while loading channel videos: \(error)")
}
// Hide the activity indicator.
self.viewWait.isHidden = true
})
return true
}
// MARK: Custom method implementation
func performGetRequest(_ targetURL: URL!, completion: #escaping (_ data: Data?, _ HTTPStatusCode: Int, _ error: NSError?) -> Void) {
// let request = NSMutableURLRequest(url: targetURL)
// request.httpMethod = "GET"
var request = URLRequest(url: targetURL)
request.httpMethod = "GET"
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
/* let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: NSError?) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
completion(data, (response as! HTTPURLResponse).statusCode, error)
})
} as! (Data?, URLResponse?, Error?) -> Void)*/
/* let task = session.dataTask(with: request, completionHandler: ({ (data: Data?, response: URLResponse?, error: NSError?) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
completion(data as Data?, (response as! HTTPURLResponse).statusCode, error)
})
} as! (Data?, URLResponse?, Error?) -> Void))*/
let task = session.dataTask(with: request) { data, response, error in DispatchQueue.main.async { completion(data, (response as! HTTPURLResponse).statusCode, error as? NSError) } }
task.resume()
}
First of all the JSON dictionary representation in Swift 3 is [String:Any] (aka Dictionary<String,Any>)
Second of all in Swift 3 all parameter labels in closures have been removed
func performGetRequest(_ targetURL: URL, completion: #escaping (Data?, Int, NSError?) -> Void) {
Do not use implicit unwrapped optionals for method parameter types. Either use regular optional (?) or non-optional.
Error 403 means Forbidden Access. Make sure you have the correct apiKey from google/youtube developer.
I also used the appcoda youtube api tutorial (which is in Swift 2 I think) and this is a working version of mine for swift 3.
func getVideosForChannelAtIndex() {
let urlString = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(playlistID)&maxResults=\(maxResults)&key=\(apiKey)"
// Create a NSURL object based on the above string.
let targetURL = URL(string: urlString)
// Fetch the playlist from Google.
performGetRequest(targetURL!) { (data, HTTPStatusCode, error) -> Void in
if HTTPStatusCode == 200 && error == nil {
do {
self.videos = []
// Convert the JSON data into a dictionary.
let resultsDict = try JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<AnyHashable, Any>
// Get all playlist items ("items" array).
let items:Array<Dictionary<AnyHashable, Any>> = resultsDict["items"] as! Array<Dictionary<AnyHashable, Any>>
// Use a loop to go through all video items.
// for var i=0; i<items.count; ++i
for i in 0 ..< items.count {
let playlistSnippetDict = (items[i] as Dictionary<AnyHashable, Any>)["snippet"] as! Dictionary<AnyHashable, Any>
let video = Video()
video.title = playlistSnippetDict["title"] as? String
// video.thumbnail =
video.videoId = (playlistSnippetDict["resourceId"] as? Dictionary<AnyHashable, Any>)?["videoId"] as? String
guard let thumbnail = ((playlistSnippetDict["thumbnails"] as? Dictionary<AnyHashable, Any>)?["high"] as? Dictionary<AnyHashable, Any>)?["url"] as? String else {
video.thumbnail = UIImage(named: "Icon1024x1024")
return
}
guard let url:URL? = URL(string: thumbnail), let data:Data? = try? Data(contentsOf: url!) else {
video.thumbnail = UIImage(named: "Icon1024x1024")
return
}
if let dataImage = data {
video.thumbnail = UIImage(data: dataImage)
} else {
video.thumbnail = UIImage(named: "Icon1024x1024")
}
self.videos.append(video)
// Reload the tableview.
self.tblVideos.reloadData()
}
} catch {
print("json error: \(error)")
}
} else {
print("")
print("HTTP Status Code = \(HTTPStatusCode)")
print("")
//Show alertDialog here with Error
print("Error while loading videos: \(error?.localizedDescription)")
let alert = UIAlertView(title: "Oops!", message: error?.localizedDescription, delegate: self, cancelButtonTitle: "OK")
alert.show()
}
// Hide the activity indicator.
self.viewWait.isHidden = true
}
}
This is for the performGetRequest
func performGetRequest(_ targetURL: URL, completion: #escaping (_ data: Data?, _ HTTPStatusCode: Int?, _ error: Error?) -> Void) {
var request = URLRequest(url: targetURL)
request.httpMethod = "GET"
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
let task = session.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
completion(data, (response as? HTTPURLResponse)?.statusCode, error)
})
}
task.resume()
}