import Foundation

struct Constants {

    struct URLs {

        static func weather(city: String) -> String { "http://api.openweathermap.org/data/2.5/weather?q=\(city)&appid=yourapikey&units=imperial" }
        // celsius = metric

    }

}

import Foundation

struct WeatherResponse: Decodable {
    let main: Weather
}

struct Weather: Decodable {

    let temp: Double?
    let humidity: Double?

    static var placeholder: Weather {
        return Weather(temp: nil, humidity: nil)
    }

}

class Webservice {

    func fetchWeather(city: String) -> AnyPublisher<Weather, Error> {
        guard let url = URL(string: Constants.URLs.weather(city: city)) else {
            fatalError("Invalid URL")
        }
        return URLSession.shared.dataTaskPublisher(for: url)
            .map { $0.data }
            .decode(type: WeatherResponse.self, decoder: JSONDecoder())
            .map { $0.main }
            .receive(on: RunLoop.main)
            .eraseToAnyPublisher()
    }

}

import UIKit
import Combine

class ViewController: UIViewController {

    @IBOutlet weak var cityTextField: UITextField!
    @IBOutlet weak var temperatureLabel: UILabel!

    private var webservice: Webservice = Webservice()
    private var cancellable: AnyCancellable?

    override func viewDidLoad() {
        super.viewDidLoad()

        setupPublishers()


        //        self.cancellable = self.webservice.fetchWeather(city: "Houston")
        //            .catch{_ in Just(Weather.placeholder)}
        //            .map { weather in
        //                if let temp = weather.temp {
        //                    return "\(temp)"
        //                } else {
        //                    return "Error getting weather!"
        //                }
        //            }
        //            .assign(to: \.text, on: self.temperatureLabel)


    }

    private func setupPublishers() {

        let publisher = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: self.cityTextField)

        self.cancellable = publisher.compactMap {
            ($0.object as! UITextField).text?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
        }.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .flatMap { city in
                return self.webservice.fetchWeather(city: city)
                    .catch { _ in Just(Weather.placeholder) }
                    .map { $0 }
            }.sink {

                if let temp = $0.temp {
                    self.temperatureLabel.text = "\(temp)"
                } else {
                    self.temperatureLabel.text = ""
                }
            }
    }

}

GitHub

View Github