0

I am new to coding and SwiftUI in general, I am coding a crypto portfolio app I am doing the candle stick chart using apple chart API, get my API data fetched. sorry if my code is messy.

JSON from market data rest API the "60" means the number of seconds and so on till the number of sec of 1 week with prices of the coin in the squared brackets

  {
"result": {
    "60": [
      [
    1665841920,
    19131.1,
    19131.8,
    19131.1,
    19131.8,
    0.1343188,
    2569.67054912
  ],
  [
    1665841980,
    19130.8,
    19130.8,
    19130.8,
    19130.8,
    0.05614383,
    1074.076382964



]
       ],
          "180": [ ] 
       },
         "allowance": {
           "cost": 0.015,
           "remaining": 7.33,
           "upgrade": "For unlimited API access, create an account at https://cryptowat.ch"
         }
       }

here is my data model

import Foundation
import SwiftUI
// MARK: - Result
struct Result: Codable {

let result: [String: [[Double]]]?
let allowance: Allowance
}

// MARK: - Allowance
struct Allowance: Codable {

let cost, remaining: Double
let upgrade: String
}

here is my CoinNetworkManager where i do the networking

func loadData(){
        // the url to request data from
    let urlAlone = "https://api.cryptowat.ch/markets/kraken/btcusd/ohlc"
    
    if let url = URL(string: urlAlone) {
            // requesting data using URL session and dataTask, getting 3 parameters back, and put em in dat, response, error
            // data is what the data we get back that we will be decoding and put em into our data object
        let session = URLSession.shared
        let task = session.dataTask(with: url){ data,response,error in
                // if no error do this
                // we will unwrap the optional data into unwrappedData and decode it using JSONDecoder
            if error == nil{
                print("task complete")
                

let decoder = JSONDecoder()
                    if let safeData = data {
                        do{
                            
                        let decodedData = try decoder.decode(Result.self, from: safeData)
                        DispatchQueue.main.async {
                                // and pass the result to coinsData
                            self.result = decodedData
                            print(self.result.result?["3600"]?[1][2] ?? 0)
                            print(self.result.result?["3600"]?[1] ?? 0)
                        }
                    } catch {
                        print(error.localizedDescription)
                    }
                }
                
            }
            
        }
        task.resume()
            
    }
    
    
}

printing above get me this in the console

19727.0 [1662278400.0, 19701.4, 19727.0, 19631.7, 19637.1, 24.43309418, 480989.386594268]

but when i try to put it in a chart i where i assume i will access through the index of the data array but i get: Fatal error: Index out of range how do i properly access the dict/ array [String:[Double]], was i formatted incorrectly or the method?

thank you in advance everyone

Chart code here

struct ContentView: View {
@ObservedObject var coinNM: CoinNetworkManager
    //  var pricesArray: [Double]

var body: some View {
        //      pricesArray = coinNM.result.result?["3600"] as! [Double]
    
VStack {
    Chart(coinNM.result.result?["60"] ?? [[0.4],[0.5]], id: \.self) { price in
        RectangleMark(x: .value("Close Time", price[0]),
                      yStart: .value("Low Price", price[1]),
                      yEnd: .value("High Price", price[0]), width: 4)

    }
Jono Then
  • 3
  • 6
  • What you have is some badly structured json unfortunately where the inner array should really have been a dictionary. Personally I would first have mapped that inner array to my own custom type so that I can use a struct instead when building the chart which would make the code much clearer. – Joakim Danielson Oct 16 '22 at 11:03
  • Hey Danielson, how would you structure your inner array to your own custom type? had few JSON before this one is hard to work with for me, I used quicktype site to help me structure my data model – Jono Then Oct 16 '22 at 11:17
  • Doesn't the api supply some kind of documentation? My struct would contain a timestamp property of type Date and then I am guessing you have open, high, low and close price. The two last ones are more uncertain but maybe change and turnover. – Joakim Danielson Oct 16 '22 at 11:19
  • yes, index 0 is the timestamp, shoulda used another var name but you know can't think of anything else atm – Jono Then Oct 16 '22 at 11:21
  • yes it does, the values in the array read like this [ CloseTime, OpenPrice, HighPrice, LowPrice, ClosePrice, Volume, QuoteVolume ] quite a brief doc tbh i can access it fine using the index after fetching the data, but not after i put it into ForEach for charts – Jono Then Oct 16 '22 at 11:25
  • That is most likely because you are using hard coded indexed and the fact that you are downloading data asynchronously so when the view code is first executed you have no data yet and the default values you have given is used and they only contain one element each so calling `price[1]` will crash. – Joakim Danielson Oct 16 '22 at 11:32
  • _hard coded indexed_, should have been "hard coded indices" – Joakim Danielson Oct 16 '22 at 11:37
  • doesn't the DispatchQueue.main.async on my fetchdata func take care of that tho? or it will still crash due to hard-coded indexed? a rough idea of how you will structure the data model will be amazing man thanks for the response – Jono Then Oct 16 '22 at 11:40
  • DispatchQueue.main.async has nothing to do with this. Look again at the default values you have supplied to the chart. – Joakim Danielson Oct 16 '22 at 11:42

1 Answers1

0

In ContentView inside Chart, try this approach where you check the price (an array of Double) size, to solve your index out of range error:

Chart(coinNM.result.result?["60"] ?? [[0.4],[0.5]], id: \.self) { price in
    if price.count > 1 {  // <-- here
        RectangleMark(x: .value("Close Time", price[0]),
                      yStart: .value("Low Price", price[1]),  // <-- because
                      yEnd: .value("High Price", price[0]), width: 4)
    }
} 

Or this, depending on what you want to show:

Chart(coinNM.result.result?["60"] ?? [[0.4],[0.5]], id: \.self) { price in
    if price.count > 3 {  // <-- here
        RectangleMark(x: .value("Close Time", price[0]),
                      yStart: .value("Low Price", price[3]),  // <-- because
                      yEnd: .value("High Price", price[2]), width: 4)
    }
}
  • Hey Man, it worked thanks a lot but how does the if statement check the size of the array and solve the out of range tho? the array in fact has more than 1 index, is it bc of the default values i provided since it was optional? – Jono Then Oct 18 '22 at 09:27
  • The array is initially empty (or nil), so while the data is being fetched with `func loadData()`, an asynchronous function, the UI is refreshed. At that time the index will be out of range (if not checked). Some milliseconds later, the array will have the data and the UI will refresh again. And all works well now with the `if price.count > ...` check. If my answer helped, could you mark it as accepted, with the tick mark. – workingdog support Ukraine Oct 19 '22 at 03:14
  • Muchly appreciated ser – Jono Then Oct 19 '22 at 08:37