-1

I want to make a Swift (5.4.6) app to display information about cryptos. For this I need to call a server to get the information I want to display. However my application don't wait for the data to be processed so I get an error for wanting to display a nil.

Here is my code for the call to the server :

        
        var cryptos = Cryptos(cryptos: nil)
        
        let url = URL(string: urlString)
        let session = URLSession.shared
        let dataTask = session.dataTask(with: url!) {(data, response, error) in
            if error == nil && data != nil {
                let decoder = JSONDecoder()
                do {
                    cryptos = try decoder.decode(Cryptos.self, from: data!)
                    print(cryptos.cryptos![6].name)
                }
                catch {
                    print("Impossible de convertir les données reçues")
                }
            }
        }
            dataTask.resume()
        return cryptos
    }

Here are the struct I want to decode the json into :

struct Crypto: Codable {
    let name: String
    let symbol: String
    let price: Float
    let totalSupply: Float
    let marketCap: Float
    let change24h: Float
}

struct Cryptos: Codable {
    let cryptos: [Crypto]?
}

Finally here is the code in my view :


var lesCryptos = Cryptos()

struct CryptoList: View {
    var body: some View {
        HStack {
            NavigationView {
            }
        }
        .onAppear(perform: getAllCryptosInfos)
    }
    
}

func getAllCryptosInfos() {
    lesCryptos = Worker().getAllCryptosInfos()
    print(lesCryptos.cryptos![7].name)
}

The error appears when I want to print the name ("lesCryptos.cryptos!" is nil)

Also here is a sample of the JSON i get :

{
  "cryptos": [
    {
      "name":"Bitcoin",
      "symbol":"BTC",
      "price":36301.41347043701,
      "totalSupply":18728856,
      "marketCap":679883945484.275,
      "change24h":0.36443243
    },
    {
      "name":"Ethereum",
      "symbol":"ETH",
      "price":2784.5450982190573,
      "totalSupply":116189845.499,"marketCap":323535864747.07007,
      "change24h":3.46116544
    }
  ]
}
MVT KVM
  • 168
  • 10
  • 1
    This is SwiftUI, there’s no need to wait. *Publish* `cryptos`. [Here](https://stackoverflow.com/questions/67858815/how-to-show-parsed-data-with-swiftui#comment119947147_67858815) is a very similar question I answered just yesterday. – vadian Jun 07 '21 at 11:45
  • Thank you for your response, but I am very new to the swift world, do you have any examples? – MVT KVM Jun 07 '21 at 11:47
  • You can't return from an async method. And the way you do it, the return executes before the closure has finished. See [this example](https://stackoverflow.com/a/31264556/2227743) (and there's plenty of other ones on the site). Start by studying this then you will be able to adapt to your own case. – Eric Aya Jun 07 '21 at 12:31

2 Answers2

0

First, convert your struct to a class and setup default values so that there is no nil values to cause crashes.

class Crypto: Codable {
    let name: String
    let symbol: String
    let price: Float
    let totalSupply: Float
    let marketCap: Float
    let change24h: Float

    init() {
        name = ""
        symbol = ""
        price = 0.0
        totalSupply = 0.0
        marketCap = 0.0
        change24h: 0.0
    }
}

In my example, notice that the init() constructor takes in no values. This allows me to create an object Crypto() without passing any values. This means that my object will have those default values, which is useful in your case because you're crashing from nil values.

struct ExampleView: View {
     @State var someCrypto = Crypto()

     var body: some View {
         Text("\(someCrypto.name)"
              .onAppear(
                   someCrypto.name = "Example"
              )
     }
}

In this example I'm using my default constructor for my Crypto object. That ensures that my view can display the name "" given by someCrypto.name. Then I use onAppear to simulate fetching of your data from the API. You will notice that the view updates automatically. This is because of the @State object which essentially tells the view to listen for any changes, and those changes are two-way. While in this context a Text() is not actually going to use it two-way a TextField() would.

Additional reading and studying should be looked up for @State @ObservedObject @ObservableObject and @Published which will help you get off your feet and use SwiftUI much more responsively.

xTwisteDx
  • 2,152
  • 1
  • 9
  • 25
0

Thank you for all your responses, however I managed to resolve my problem using completionHandler. Here is the link to the question and answers that helped me : stackoverflow question

MVT KVM
  • 168
  • 10