0

I was trying to make a weather api call, the api call needs to have a location. The location that I pass is a variable, but now I want to change the location value based on a TextField's input.

I made the apiKey shorter just for safety measures. There's more code, but it's not relevant. I just need to know how to change the city variable that is on the WeatherClass using the TextField that is in the cityTextField View.

Thanks.

class WeatherClass: ObservableObject {
    @Published var weatherAddress: String = ""
    @Published var weatherDays: [WeatherDays] = []
    var city: String = ""
  
    func fetch() {
        let location = city
        let apiKey = "AP8LUYMSTHZ"
        
        let url = URL(string: "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/\(location)?key=\(apiKey)")!
            URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
                
            if let weather = try? JSONDecoder().decode(WeatherData.self, from: data) {
                DispatchQueue.main.async {
                    
                    self.weatherAddress = weather.resolvedAddress
                    self.weatherDays = weather.days
                }
            } else {
                print("City?")
            }
        }.resume()
    }//----------------------------------- End of fetch()
}
struct WeatherData: Decodable {
    let resolvedAddress: String
    let days: [WeatherDays]
}
struct WeatherDays: Hashable, Decodable {
    let datetime: String
    let tempmax: Double
    let tempmin: Double
    let description: String
}

struct cityTextField: View {
    
    @State var city: String = ""
    
    var body: some View {
       
            TextField("Search city", text: $city).frame(height:30).multilineTextAlignment(.center).background().cornerRadius(25).padding(.horizontal)
    } 
}

I already watched a lot of tutorials for similar things buts none of them really helped me.

2
  • Have you tried using @Published on the city var in your WeatherClass, rather than binding it to the @State var in your View? Commented Nov 15, 2022 at 4:58
  • @synapticloop I haven't, it's late here so I'm going to try it tomorrow. Thanks for the comment anyways. I don't know if this is the correct way to reply to you'r comment because i'm new here haha. Commented Nov 15, 2022 at 5:04

1 Answer 1

3

Try this approach using minor modifications to func fetch(_ city: String){...} to fetch the weather for the city in your TextField using .onSubmit{...}

struct ContentView: View {
    @StateObject var weatherModel = WeatherClass()
    
    var body: some View {
        VStack {
            cityTextField(weatherModel: weatherModel)
        }
    }
}

struct cityTextField: View {
    @ObservedObject var weatherModel: WeatherClass  // <-- here
    @State var city: String = ""
    
    var body: some View {
            TextField("Search city", text: $city)
            .frame(height:30)
            .multilineTextAlignment(.center)
            .background()
            .cornerRadius(25)
            .padding(.horizontal)
            .onSubmit {
                weatherModel.fetch(city)  // <-- here
            }
    }
}

class WeatherClass: ObservableObject {
    @Published var weatherAddress: String = ""
    @Published var weatherDays: [WeatherDays] = []

    func fetch(_ city: String) {  // <-- here
        let apiKey = "AP8LUYMSTHZ"
        
        // -- here
        let url = URL(string: "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/\(city)?key=\(apiKey)")!
        
            URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            if let weather = try? JSONDecoder().decode(WeatherData.self, from: data) {
                DispatchQueue.main.async {
                    self.weatherAddress = weather.resolvedAddress
                    self.weatherDays = weather.days
                }
            } else {
                print("City?")
            }
        }.resume()
    }
}

Alternatively, as suggested by synapticloop, you could use this approach:

struct cityTextField: View {
    @ObservedObject var weatherModel: WeatherClass  // <-- here

    var body: some View {
        TextField("Search city", text: $weatherModel.city) // <-- here
            .frame(height:30)
            .multilineTextAlignment(.center)
            .background()
            .cornerRadius(25)
            .padding(.horizontal)
            .onSubmit {
                weatherModel.fetch()  // <-- here
            }
    }
}

class WeatherClass: ObservableObject {
    @Published var weatherAddress: String = ""
    @Published var weatherDays: [WeatherDays] = []
    @Published var city: String = "" // <-- here

    func fetch() {
        let apiKey = "AP8LUYMSTHZ"
        
        // -- here
        let url = URL(string: "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/\(city)?key=\(apiKey)")!
        
            URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            if let weather = try? JSONDecoder().decode(WeatherData.self, from: data) {
                DispatchQueue.main.async {
                    self.weatherAddress = weather.resolvedAddress
                    self.weatherDays = weather.days
                }
            } else {
                print("City?")
            }
        }.resume()
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Sorry I don't know if this is the right way to replay you, also I don't know how to tag you, but thanks for the message I will try your suggestion tomorrow.
If my answer helped, click the tick mark near the answer, it turns green, thanks.
Thanks, I think that's going to work, either way I'm going to wait until tomorrow to try it and mark your answer with the tick mark, hope you don't mind.
Hi, I have tried the synapticloop way first and it worked. Either way you helped a lot by writing what actually had to change. I'm almost 4 months into programming, so this was really tough to make. Now I want to submit the value by clicking a button in the UI, I'll give it a try by my self. Thanks for the time!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.