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:
- 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
}
}
- Create simple ViewModel which will retrieve data from our
NetworkService
and prepare to present it inContentView
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")
}
}
}
}
}
}
- Add our
ContentViewVM
to ourContentView
:
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.