Home > front end >  How to represent a JSON file in SwiftUI?
How to represent a JSON file in SwiftUI?

Time:12-26

I'm quite a beginner and trying to get the OpenWeather API JSON to show up in my challenge project.

I managed to model it


struct WeatherRespose: Codable {
    var weather: [Weather]
    var name: String
}

&

import Foundation


struct Weather: Hashable, Codable {
    var main: String
    var description: String
}

In addition to fetch the data in ContentView. However, when I try to present it:

@State var weatherForcast = Weather() or @State var weatherForcast = WeatherResponse() I get this error: Missing argument for parameter 'from' in call, insert 'from: <#Decoder#>'

The only thing that worked for me is to present the data in an array:

@State var weatherForcast = [Weather]()

Any idea what am I missing? thank you so much! Ran

CodePudding user response:

I made pretty simple example of how you can do this. There are several additional files, so it's easier to understand how it works in details:

  1. Create additional file called NetworkService, it will fetch weather data:
import Foundation

final class NetworkService{

    private let url = "https://example.com"
    
    func performWeatherRequest(completion: @escaping (Result<WeatherResponse, Error>) -> Void){
            URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
                guard let data = data, error == nil else {
                    completion(.failure(WeatherError.failedToDownload))
                    return
                }
                                
                let weatherResponse: WeatherResponse = try! JSONDecoder().decode(WeatherResponse.self, from: data)
            
                completion(.success(weatherResponse))
                
            }.resume()
    }
    public enum WeatherError: Error {
            case failedToDownload
    }
}
  1. Create simple ViewModel which will retrieve data from our NetworkService and prepare to present it in ContentView
import Foundation
import SwiftUI

extension ContentView {
    @MainActor class ContentViewVM: ObservableObject {
        private var networkService = NetworkService()
        
        @Published var currentWeatherMain: String?
        @Published var currentWeatherDescription: String?
        
        func fetchWeather(){
            networkService.performWeatherRequest { [weak self] result in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let weatherResponse):
                        self?.currentWeatherMain = weatherResponse.weather[0].main
                        self?.currentWeatherDescription = weatherResponse.weather[0].description
                    case .failure(_):
                        print("oops, error occurred")
                    }
                }
            }
        }
    }
}
  1. Add our ContentViewVM to our ContentView:
import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ContentViewVM()
    
    var body: some View {
        VStack {
            Text("The main is: \(viewModel.currentWeatherMain ?? "")")
            Text("The description is: \(viewModel.currentWeatherDescription ?? "")")
        }
        
        .onAppear{
            viewModel.fetchWeather()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Hope it helps.

CodePudding user response:

It looks like you're trying to create an instance of the Weather or WeatherResponse type, but you're missing the required from parameter in the initializer.

The Codable protocol provides a default implementation of the init(from:) initializer, which is used to initialize an instance of a type from a decoder object. When you try to create an instance of a Codable type, you need to pass in a decoder object as the from parameter.

For example, to create an instance of the WeatherResponse type, you could do something like this:

let decoder = JSONDecoder()
let weatherResponse = try decoder.decode(WeatherResponse.self, from: jsonData)

Here, jsonData is the data you've received from the OpenWeather API, in the form of a Data object. The decode(_:from:) method of the JSONDecoder will try to parse the data and initialize a new WeatherResponse object using the data.

If you're trying to use the @State property wrapper to store the weather data in your SwiftUI view, you'll need to pass the decoder object and the data to a method that can update the state. For example:

struct ContentView: View {
    @State var weatherForcast: WeatherResponse = WeatherResponse()

    var body: some View {
        // ...
    }

    private func updateWeather(from decoder: JSONDecoder, data: Data) {
        do {
            let weatherResponse = try decoder.decode(WeatherResponse.self, from: data)
            self.weatherForcast = weatherResponse
        } catch {
            // Handle error
        }
    }
}

Then, you can call the updateWeather(from:data:) method from within your view to update the state with the new weather data.

  • Related