Ive got an answer from an XML API that comes back to me as a String. I need it to be an int so that i can add it to another value in laters on. Ive tried to unwrap it and read it as an int but it didnt work. Ive also tried trimming blank spaces and then unwrap it but that didnt work either.
If i set the leading let value: Int it will give me an error saying that the value is not in the correct format.
What i have so far is this:
struct HydroData: Decodable {
let value: String
let textTranslationId: String?
let titleTranslationId: String?
let style: String?
}
struct HydroResult: Decodable {
let HydroData: [HydroData]
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
calcIndex()
let url = URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("No data")
return
}
do {
let result = try JSONDecoder().decode(HydroResult.self, from: data)
if let seDesc = result.HydroData.filter({ $0.titleTranslationId == "ProductionConsumption.HydroSEDesc" }).first {
let hydroValue = seDesc.value
print(seDesc.value)
} else {
print("Error: no value")
}
} catch {
print(error.localizedDescription)
}
}
task.resume() }
func calcIndex(){
let newHydro = hydroValue + 1000
print(newHydro)
}
}
You need to use initializer for Int that accepts String as parameter Int(). Also, I've fixed the issue you're gonna face when you try to use the Int(seDesc.value) because it contains a non-decimal-digit character. Here's the entire code:
class ViewController: UIViewController {
var hydroValue = 0
override func viewDidLoad() {
super.viewDidLoad()
calcIndex()
let url = URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("No data")
return
}
do {
let result = try JSONDecoder().decode(HydroResult.self, from: data)
if let seDesc = result.HydroData.filter({ $0.titleTranslationId == "ProductionConsumption.HydroSEDesc" }).first {
let value = seDesc.value.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
self.hydroValue = Int(value) ?? 0
print(value)
self.calcIndex()
} else {
print("Error: no value")
}
} catch {
print(error.localizedDescription)
}
}
task.resume()
}
func calcIndex(){
let newHydro = hydroValue + 1000
print(newHydro)
}
}
Related
Hello i have a CollectionViewCell file, where i am trying to call public func configure cell.
Here is func
public func configureCell(with cellViewModel: CellViewModel) {
self.articleTitleLabel.text = cellViewModel.title
if let data = cellViewModel.imageData {
self.articleImage.image = UIImage(data: data)
} else if let url = cellViewModel.urlToImage {
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data && error == nil else { return }
cellViewModel.imageData = data
DispatchQueue.main.async {
self.articleImage.image = UIImage(data: data)
}
}
}
}
here is model
struct CellViewModel {
let title: String
let urlToImage: String?
let imageData: Data? = nil
init(title: String, urlToImage: String) {
self.title = title
self.urlToImage = urlToImage
}
}
But i got error:
No exact matches in call to instance method 'dataTask'
Why? How can i fix my code?
urlToImage is of type String but the datatask needs an argument of type URL.
You can use:
else if let stringurl = cellViewModel.urlToImage, let url = URL(string: stringurl){
I'm new to swift i am sorry if this is a stupid question
I am trying to expand my knowledge in macOS development and i am trying out new things
i am parsing a json file from an url
it works fine in the do{}catch{} brackets however, i want to use what i get from the json data in other parts of the program.
i created some variables to store the values.
However, they go back to their initial value once the do{}catch{} execution is done
how can i store the values I got
#IBAction func buttonPressed(_ sender: Any) {
var summonerNameGlobal: String = ""
var summonerIdGlobal: String = ""
var summonerPuuidGlobal: String = ""
var summonerAccountIdGlobal: String = ""
let jsonString = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/john?api_key=\(apiKey)"
guard let url = URL(string: jsonString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
DispatchQueue.main.async {
do {
let summoner = try JSONDecoder().decode(SummonerInfo.self, from: data)
self.summonerIdLabel.stringValue = summoner.id
summonerNameGlobal = summoner.name
summonerIdGlobal = summoner.id
summonerAccountIdGlobal = summoner.accountId
summonerPuuidGlobal = summoner.puuid
} catch {
print(error)
}
}
}.resume()
print(summonerNameGlobal)
print(summonerPuuidGlobal)
print(summonerIdGlobal)
print(summonerAccountIdGlobal)
}
They are not going to default again but you are checking them before they are being set ... because async function take some time to get response from server but your print statements run immediately
What you can do is to check values once they are set
func callApi(completion: #escaping (SummonerInfo?)->Void){
let jsonString = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/john?api_key=\(apiKey)"
guard let url = URL(string: jsonString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
DispatchQueue.main.async {
do {
let summoner = try JSONDecoder().decode(SummonerInfo.self, from: data)
completion(summoner)
} catch {
completion(nil)
print(error)
}
}
}.resume()
}
#IBAction func buttonPressed(_ sender: Any) {
callApi { [weak self] info in
if let getInfo = info {
print(getInfo.name)
print(getInfo.id)
print(getInfo.accountId)
print(getInfo.puuid)
} else {
print("data is nil")
}
}
}
I am working on an app that Decode a JSON file and creates three variables out of the function: Status,emptySlots,freeBikes. I want to assign these values to labels. However, no matter what I do, I was unable to get any output with any method.
The function code:
func getBikeData(stationName:String){
if let url = URL(string: "https://api.citybik.es//v2/networks/baksi-bisim"){
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data, error == nil else {
print(error?.localizedDescription ?? "Response Error")
return
}
do {
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: [])
print(jsonResponse) //Response result
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
//Decode JSON Response Data
let model = try decoder.decode(ResponseJSON.self, from: dataResponse)
print(model)//Output - 1221
if let station = model.network.stations.first(where: { $0.name == stationName }) {
//get the properties of station here as per requirement
let emptySlots: Int = station.empty_slots
let freeBikes: Int = station.free_bikes
let Status: String = station.extra.status
print(emptySlots, freeBikes, Status)
}
}
catch let parsingError {
print("Error", parsingError)
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
}
Any help is gladly appreciated. I have already tried to return the variables, and using completion block.
ResponseJSON Struct:
struct ResponseJSON: Codable {
let network: Stations
}
One way to solve this is to use a closure. To simplify things create a struct that holds the values
struct BikeResponse {
let status: String
let freeBikes: Int
let emptySlots: Int
}
And then modify your function declaration to
func getBikeData(stationName:String, completion: (BikeResponse)->(Void)){
and then after the decoding you call the completion handler
if let station = model.network.stations.first(where: { $0.name == stationName }) {
let response = BikeResponse(status: station.extra.status,
freeBikes: station.free_bikes,
emptySlots: station.empty_slots)
completion(response)
And then in your completion code you can assign the values to your labels
getBikeData(stationName: "ABC") { response in
DispatchQueue.main.async {
someLabel.text = response.status
//...
}
}
Simplest solution:
if let station = model.network.stations.first(where: { $0.name == stationName }) {
DispatchQueue.main.async {
self.emptySlotsLabel.text = String(station.empty_slots)
self.freeBikesLabel.text = String(station.free_bikes)
self.statusLabel.text = station.extra.status
}
}
emptySlotsLabel, freeBikesLabel and statusLabel are the labels, change the names to the real names
you need to add completion handler to you function. Because you are trying to make async query.
After calling getbikedata func you can assign value to your labels.
Sample code looks like this:
func getBikeData(stationName:String, completion: #escaping (Station) -> Void) {
// ... your code here
if let station = model.network?.stations?.first(where: { $0.name == stationName }) {
//get the properties of station here as per requirement
// let emptySlots: Int = station.emptySlots!
// let freeBikes: Int = station.freeBikes!
// let Status: String = (station.extra?.status)!.rawValue
completion(station)
}
// ... other your code here
}
And usage:
getBikeData(stationName: stationName) { (station) in
print(station)
// For example
label.text = station.emptySlots
}
I'm attempting to print/dump and array of type Music outside of a function it's created in. I can successfully dump the musicItems array inside of the getMusicData function but when I set the musicItems array outside of the scope, it won't print anything. What am I doing wrong with the scope here? I have a feeling it's super simple but I just can't figure it out. Thanks in advance for taking the time to read this.
edit: It's giving me "0 elements" in the console when I attempt to dump the musicItems array in the ViewController class. Well, the function is in the same class as well so I guess I don't know what to call the first array. The parent array?
struct MusicResults: Decodable {
let results: [Music]?
}
struct Music: Decodable {
let trackName: String?
let collectionName: String?
let artworkUrl30: String?
}
class ViewController: UITableViewController, UISearchBarDelegate {
var musicItems: [Music] = []
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData()
dump(musicItems)
}
Here is the function.
func getMusicData() {
var musicItems: [Music] = []
guard let searchTerm = searchString else {return}
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
// print(results.trackName!)
musicItems.append(results)
}
//dump(musicItems)
self.musicItems = musicItems
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
Fixed Code
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData {
music in
dump(music)
}
function:
func getMusicData(completionHandler: #escaping (_ music: [Music]) -> ()) {
...
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
musicItems.append(results)
}
completionHandler(musicItems)
...
Your 'getMusicData' function is asynchronous which means that when it executes, it queues data task in a background queue and proceeds the execution and since there are no more institutions it simply returns control to its calling site - 'musicButton()' action, which in its turn executes the next instruction - prints the 'musicItems' array which might (and most likely, is) still not populated as the network call haven’t yet completed. One of the options that you have here is to pass a completion block to your 'getMusicData' function, that runs it after data task gets the results.
Another option is to use Property Observers
var musicItems: [Music] = [] {
didSet {
dump(self.musicItems)
/// This is where I would do the...
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
and then
func getMusicData() {
guard let searchTerm = searchString else { print("no search"); return }
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { print("url error"); return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { print(err ?? "unknown"); return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
if let results = music.results {
self.musicItems.append(contentsOf: results)
}
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
I successfully fetched and decoded data from an API and now have access to all the data I need to be used in the algorithm I want to write in my App.
The issue is that I don't know how to access this data after I decoded it, I can print it immediately after it's decoded but I have no idea how to use it in another function or place in my app.
Here is my Playground:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError : Error {
case FoundNil(String)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
print(marketData.data[0].open)
print(marketData.data[1].open)
print("Average=", (marketData.data[0].open + marketData.data[1].open) / 2)
//completion(marketData, nil)
throw MyError.FoundNil("data")
}
} catch {
print(error)
}
}
task.resume()
}
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
How can I use .data[0], .data[1], ..., somewhere else?
You data will be available in your fecthData() call. Probably what you want is your items variable, where you're printing it. But make sure to call the completion in your fetchData implementation.
WARNING: Untested code.
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError: Error {
case FoundNil(String)
case DecodingData(Data)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, MyError.FoundNil("data"))
}
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
completion(marketData, nil)
} else {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
} catch {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
}
task.resume()
}
fetchData() { items, error in
if let error == error {
switch(error) {
case .foundNil(let whatsNil):
print("Something is nil: \(whatsNil)")
case .decodingData(let data):
print("Error decoding: \(data)")
}
} else {
if let items = items {
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
print(items)
} else {
print("No items to show!")
}
}
}
I don't understand what is your real issue, because you have written everything you need here, but as far I understand , to pass data
just uncomment this line completion(marketData, nil)
and in
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
items is an object of your struct Response. You can pass this anywhere in your other class , by just creating an another variable like:
var items : Response!
for example :
class SomeOtherClass : NSObject{
var items : Response!
func printSomeData()
{
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
}
}
and in fetchData method write this:
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
let otherObject = SomeOtherClass()
otherObject.items = items
otherObject.printSomeData()
}