UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: How to pass the JSON decoded data to UI?

Forums > SwiftUI

Hi all,

I was able to fetch a weather data from the internet in JSON format using the same <loadData()> function from CupCakeCorner app. Now, I am not able to get the decoded data and pass to my UI elements.

I was able to print all the data coming from the URL, after enter the city in the textfield and tapping the magnifier glass (print statements were removed from the code below).

See code below:

import SwiftUI

struct WeatherData_Network: Codable {

    struct Main: Codable {
        let temp: Double
        let pressure: Int
        let humidity: Int

    }

    struct Weather: Codable {
        let id: Int
    }

    let name: String
    let main: Main
    let weather: [Weather]
}

struct WeatherModel {

    let conditionId: Int
    let cityName: String
    let temperature: Double
    let pressure: Int
    let humidity: Int

    var temperatureString: String {
        return String(format: "%.1f", temperature)
    }

    var conditionString: String {
        switch conditionId {
        case 200...232:
            return "cloud.bolt"
        case 300...321:
            return "cloud.drizzle"
        case 500...531:
            return "cloud.rain"
        case 600...622:
            return "cloud.snow"
        case 701...781:
            return "cloud.fog"
        case 800:
            return "sun.max"
        case 801...804:
            return "cloud.bolt"
        default:
            return "cloud"
        }
    }
}

struct NetworkingView: View {

    @State private var location: String = ""
    @ScaledMetric var size: CGFloat = 100

    var weatherModel: WeatherModel

    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()

            VStack {
                HStack {
                    Button(action: {}) {
                        Image(systemName: "location.north.circle.fill")
                            .font(.largeTitle)
                            .padding(.horizontal)
                    }

                    TextField("Location", text: $location)
                        .textFieldStyle(.roundedBorder)
                        .multilineTextAlignment(.center)
                        .frame(width: 200)

                    Button(action: {Task{
                        await loadData()
                    }}) {
                        Image(systemName: "magnifyingglass")
                            .font(.largeTitle)
                            .padding(.horizontal)
                    }
                }
                .padding(.vertical)

                HStack {
                    Image(systemName: weatherModel.conditionString)
                        .font(.system(size: size))
                }

                Text("\(weatherModel.temperatureString) °C")
                    .font(.system(size: size))
                    .padding(.horizontal)
                Text("Pressure: \(weatherModel.pressure) mmHg")
                    .font(.headline)
                    .padding(.horizontal)
                Text("Humidity: \(weatherModel.humidity) %")
                    .font(.headline)
                    .padding(.horizontal)
                Text(weatherModel.cityName)
                    .font(.largeTitle)
                    .padding(.horizontal)

                Spacer()
            }
            .accentColor(.primary)

        }
    }

    func loadData() async {
        guard let url = URL(string:"https://api.openweathermap.org/data/2.5/weather?q=\(location)&appid=133....764bae.............c8e9....908&units=metric") else {
            print("Invalid URL")
            return
        }

        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            if let decodedResponse = try? JSONDecoder().decode(WeatherData_Network.self, from: data) {

                let id = decodedResponse.weather[0].id
                let temp = decodedResponse.main.temp
                let cityName = decodedResponse.name
                let pressure = decodedResponse.main.pressure
                let humidity = decodedResponse.main.humidity

                let weather = WeatherModel(conditionId: id, cityName: cityName, temperature: temp, pressure: pressure, humidity: humidity)

            }
        } catch {
            print("Invalid Data")
        }

    }
}

struct NetworkingView_Previews: PreviewProvider {
    static var previews: some View {
        NetworkingView(weatherModel: WeatherModel(conditionId: 200, cityName: "Celina", temperature: 21.1, pressure: 1000, humidity: 90))
    }
}

Thanks a lot! Renato.

3      

Here's probably the simplest way to fix this:

struct NetworkingView: View {

    @State private var location: String = ""
    @ScaledMetric var size: CGFloat = 100

    //1
    //make this a state property so we can change it
    //make it Optional so we don't have to pass anything in at first
    @State private var weatherModel: WeatherModel?

    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()

            VStack {
                HStack {
                    Button(action: {}) {
                        Image(systemName: "location.north.circle.fill")
                            .font(.largeTitle)
                            .padding(.horizontal)
                    }

                    TextField("Location", text: $location)
                        .textFieldStyle(.roundedBorder)
                        .multilineTextAlignment(.center)
                        .frame(width: 200)

                    Button(action: {Task{
                        await loadData()
                    }}) {
                        Image(systemName: "magnifyingglass")
                            .font(.largeTitle)
                            .padding(.horizontal)
                    }
                }
                .padding(.vertical)

                //2
                //we only want to show this stuff if we actually have
                // a valid weatherModel
                if let weather = weatherModel {
                    HStack {
                        Image(systemName: weather.conditionString)
                            .font(.system(size: size))
                    }

                    Text("\(weather.temperatureString) °C")
                        .font(.system(size: size))
                        .padding(.horizontal)
                    Text("Pressure: \(weather.pressure) mmHg")
                        .font(.headline)
                        .padding(.horizontal)
                    Text("Humidity: \(weather.humidity) %")
                        .font(.headline)
                        .padding(.horizontal)
                    Text(weather.cityName)
                        .font(.largeTitle)
                        .padding(.horizontal)
                }

                Spacer()
            }
            .accentColor(.primary)

        }
    }

    func loadData() async {
        guard let url = URL(string:"https://api.openweathermap.org/data/2.5/weather?q=\(location)&appid=133....764bae.............c8e9....908&units=metric") else {
            print("Invalid URL")
            return
        }

        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            if let decodedResponse = try? JSONDecoder().decode(WeatherData_Network.self, from: data) {
                let id = decodedResponse.weather[0].id
                let temp = decodedResponse.main.temp
                let cityName = decodedResponse.name
                let pressure = decodedResponse.main.pressure
                let humidity = decodedResponse.main.humidity

                //3
                //build a new WeatherModel from our JSON response
                //and assign it to our state property
                weatherModel = WeatherModel(conditionId: id, cityName: cityName, temperature: temp, pressure: pressure, humidity: humidity)
            }
        } catch {
            print("Invalid Data")
        }

    }
}
  1. We make weatherModel an @State property so it can be updated as the user searches for locations. We make it Optional so that we don't have to pass in a dummy value whenever our NetworkingView struct is created.
  2. We only want to display weather results if we have retrieved a weatherModel from the API call.
  3. You were on the right track here, but you need to take the new WeatherModel instance and assign it to the weatherModel property so the UI will update.

There are other ways of handling this, using view models or service classes, but this way is simple and may meet your needs. Try it, play around with it, do some research, etc.

I'll also note that you have an issue with locations that contain a space or non-ASCII character, like "Los Angeles" or "São Paolo". You need to properly encode the location string before passing it to the API call to handle those spaces and other characters.

3      

Thanks a lot! I will implement your solution. Yesterday I noticed I was getting the "Invalid URL" for the cities with space in between. I will work on that as well.

Again, thanks so much for your help. Renato.

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.