Aleksandr0009
Aleksandr0009

Reputation: 1

How to search by API OpenWeatherApi city?

I created these requests, I don’t know if I asked them correctly. If someone can tell, they will be grateful. Here is my code. I need to search for a city in the search and so that the weather is displayed in the table. Please help and tell me how to set and where to fix it. Or did I set the functions and methods incorrectly? error

"The data couldn’t be read because it is missing."

import UIKit

class SearchViewController: UIViewController,UISearchResultsUpdating, UITableViewDelegate,UITableViewDataSource {
    
    
    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var tableView: UITableView!
    
    var filteredCity: [OpenWeather] = []
    
   // var searchCity: [OpenWeather]=[]
    let wetherApi = WeatherManager()
    var cityWeather = [OpenWeather]()
    let netSer = NetworkService()
    let searchController = UISearchController()
    
    var isSearchBarEmpty: Bool {
      return searchController.searchBar.text?.isEmpty ?? true
    }

    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        searchController.searchResultsUpdater = self
        navigationItem.searchController = searchController
        tableView.delegate = self
        tableView.dataSource = self
        wetherApi.fetchCurrentWeather(city: "London")
  
      
    }
    
      
func updateSearchResults(for searchController: UISearchController) {
        self.filteredCity = self.cityWeather.filter { (city:OpenWeather) -> Bool in
            if city.city.lowercased().contains(self.searchController.searchBar.text!.lowercased()){
                return true
            }else {
                return false
            }
        }
        //Update the results TableView
        self.tableView.reloadData()

    }
   
    
    @objc func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cityWeather.count

      }
    
    
    @objc(tableView:cellForRowAtIndexPath:) internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CityTableViewCell
        
        cell.cityTemp.text = cityWeather[indexPath.row].city
        cell.cityTemp.text = "\(cityWeather[indexPath.row].main.tempCelsius)"
        
        
        return cell
    }
}

My structure weather:

  import Foundation
import UIKit

struct OpenWeather: Codable  {
    let coord:Coordinate
    let city:String
    let weathertwo:[WeatherTwo]
    let main: Main
    
   
}

struct Coordinate: Codable {
    let lan:Float
    let lot:Float
     enum CodingKeys: String, CodingKey  {
    case lan = "lat"
    case lot = "lon"
    }
}

struct WeatherTwo: Codable {
    let main: String
    let description: String 
}

struct Main: Codable {
    private let temp: Double
     var tempCelsius: Double {
       get {
           return (temp - 32) / 1.8
       }
     }
    private let temp_min: Double
     var tempCelsiusmin: Double {
       get {
           return (temp - 32) / 1.8
       }
     }
    private let temp_max: Double
     var tempCelsiusmax: Double {
       get {
           return (temp - 32) / 1.8
       }
     }
    let pressure: Int
    let humidity: Int
    
}

My code Api codable download:

struct WeatherManager {
        //Deliberately deleted "q=London" within the url, because it needs to be customizable with a different city
        
        
    func fetchCurrentWeather(city: String){
         let URL_API = "https://api.openweathermap.org/data/2.5/weather?q="
        let CITY_ID = city
        //let URL_API_KEY = "<api_key>"
        let session = URLSession(configuration: .default)
       
        
        let urlString = URL_API + CITY_ID + "&appid=cfe547d810fc4ad95e8f24187c6b08da"
            
            guard let url = URL(string: urlString) else {
                print("Error building URL")
                return
            }
            
            let task = session.dataTask(with: url) { (data, response, error) in
                
                DispatchQueue.main.async {
                    if let error = error {
                        print(error.localizedDescription)
                        return
                    }
                    
                    guard let data = data, let response = response as? HTTPURLResponse else {
                        print("Invalid data or response")
                        return
                    }
                    
                    do {
                        if response.statusCode == 200 {
                            let items = try JSONDecoder().decode(OpenWeather.self, from: data)
                            print(items)
                        } else {
                            print("Response wasn't 200. It was: " + "\n\(response.statusCode)")
                        }
                    } catch {
                        print(error.localizedDescription)
                    }
                }
                
            }
            task.resume()
        
        
        
    }
    
}

Upvotes: 0

Views: 1643

Answers (1)

Shawn Frank
Shawn Frank

Reputation: 5213

I see a few reasons this happens.

If you look at the response the API gives you, you will find that:

  1. The keys weathertwo and city do not exist in the response, yet they are present in your OpenWeather struct. You need to make them optional or provide your default values by overriding the init if you don't always expect this data to be present

  2. Have a look at the coord object in the response

"coord": {
"lon": -0.1257,
"lat": 51.5085
}

Compare the spelling of keys with your coord struct:

struct Coordinate: Codable {
    let lan:Float
    let long:Float
}

If you want to use different variable names, you need to look at using CodingKeys

However, the simplest fix I believe is changing your OpenWeather struct to this:

struct OpenWeather: Codable {
    let coord: Coordinate
    let city: String?
    let weathertwo: [WeatherTwo]?
    let main: Main
}

and fixing coords to this:

struct Coordinate: Codable {
    let lat: Float
    let lon: Float
}

should get rid of the error and give you the data you need as I can see data from the Open Weather API printed on the console:

OpenWeather(coord: TestApp.Coordinate(lat: 25.2582, lon: 55.3047), city: nil, weathertwo: nil, main: TestApp.Main(temp: 300.38, temp_min: 299.31, temp_max: 301.29, pressure: 1008, humidity: 54))

There are other improvements that could be made such as using camel case for the variables with CodingKeys but that is something you can decide later.

Give this a go and see if it resolves your issue.

Update

As you rightly pointed out, you asked how to get the list of cities supported by Open Weather.

I had a quick look, it does not look like they have an API to get a list of all the cities. Their API seems to support you sending a city name and it will send back to you the weather data for that city.

However, they provide their list of cities here

I think the file history.city.list.json.gz has all the city data which might need to be included in your app or somewhere online if you wish.

Have a look.

Upvotes: 0

Related Questions