Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialPaul M
16,370 PointsI get error 20 or MissingHTTPResponseError when running my Stormy app, can anyone tell me why?
I have no console errors, and no errors or warnings within the code. So why do I get an error?
Here is my APIClient.swift
import Foundation
public let TRENetworkingErrorDomain = "com.treehouse.Stormy.Networking"
public let MissingHTTPResponseError: Int = 10
public let UnexpectedResponseError: Int = 20
typealias JSON = [String : AnyObject]
typealias JSONTaskCompletion = (JSON?, NSHTTPURLResponse?, NSError?) -> Void
typealias JSONTask = NSURLSessionDataTask
enum APIResult<T> {
case Success(T)
case Failure(ErrorType)
}
protocol JSONDecodable {
init?(JSON: [String : AnyObject])
}
protocol Endpoint {
var baseURL: NSURL { get }
var path: String { get }
var request: NSURLRequest { get }
}
protocol APIClient {
var configuration: NSURLSessionConfiguration { get }
var session: NSURLSession { get }
func JSONTaskWithRequest(request: NSURLRequest, completion: JSONTaskCompletion) -> JSONTask
func fetch<T: JSONDecodable>(request: NSURLRequest, parse: JSON -> T?, completion: APIResult<T> -> Void)
}
extension APIClient {
func JSONTaskWithRequest(request: NSURLRequest, completion: JSONTaskCompletion) -> JSONTask {
let task = session.dataTaskWithRequest(request) { data, response, error in
guard let HTTPResponse = response as? NSHTTPURLResponse else {
let userInfo = [
NSLocalizedDescriptionKey: NSLocalizedString("Missing HTTP Response", comment: "")
]
let error = NSError(domain: TRENetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
completion(nil, nil, error)
return
}
if data == nil {
if let error = error {
completion(nil, HTTPResponse, error)
}
} else {
switch HTTPResponse.statusCode {
case 200:
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String : AnyObject]
completion(json, HTTPResponse, nil)
} catch let error as NSError {
completion(nil, HTTPResponse, error)
}
default: print("Recieved HTTP Response: \(HTTPResponse.statusCode)- not handled")
}
}
}
return task
}
func fetch<T>(request: NSURLRequest, parse: JSON -> T?, completion: APIResult<T> -> Void) {
let task = JSONTaskWithRequest(request) { json, response, error in
guard let json = json else {
if let error = error {
completion(.Failure(error))
} else {
// TODO: Implement Error Handling
}
return
}
if let value = parse(json) {
completion(.Success(value))
} else {
let error = NSError(domain: TRENetworkingErrorDomain, code: UnexpectedResponseError, userInfo: nil)
completion(.Failure(error))
}
}
task.resume()
}
}
ForecastClient.swift
import Foundation
struct Coordinate {
let lattitude: Double
let longintude: Double
}
enum Forecast: Endpoint {
case Current(token: String, coordinate: Coordinate)
var baseURL: NSURL {
return NSURL(string: "https://api.forecast.io")!
}
var path: String {
switch self {
case .Current(let token, let coordinate):
return "/forecast/\(token)/\(coordinate.lattitude),\(coordinate.longintude)"
}
}
var request: NSURLRequest {
let url = NSURL(string: path, relativeToURL: baseURL)!
return NSURLRequest(URL: url)
}
}
final class ForecastAPIClient: APIClient {
let configuration: NSURLSessionConfiguration
lazy var session: NSURLSession = {
return NSURLSession(configuration: self.configuration)
}()
private let token: String
init(config: NSURLSessionConfiguration, APIKey: String) {
self.configuration = config
self.token = APIKey
}
convenience init(APIKey: String) {
self.init(config: NSURLSessionConfiguration.defaultSessionConfiguration(),
APIKey: APIKey)
}
func fetchCurrentWeather(coordinate: Coordinate, completion: APIResult<CurrentWeather> -> Void) {
let request = Forecast.Current(token: self.token, coordinate: coordinate).request
fetch(request, parse: { json -> CurrentWeather? in
// Parse from JSON to CurrentWeather
if let currentWeatherDictionary = json["currently"] as? [String: AnyObject] {
return CurrentWeather(JSON: currentWeatherDictionary)
} else {
return nil
}
}, completion: completion)
}
}
And my ViewController.swift
import UIKit
extension CurrentWeather {
var temperatureString: String {
return "\(Int(temperature))º"
}
var humidityString: String {
let percentageValue = Int(humidity * 100)
return "\(percentageValue)%"
}
var percipitationProbabilityString: String {
let percentageValue = Int(percipitationProbability * 100)
return "\(percentageValue)%"
}
}
class ViewController: UIViewController {
@IBOutlet weak var currentTemperatureLabel: UILabel!
@IBOutlet weak var currentHumidityLabel: UILabel!
@IBOutlet weak var currentPrecipitationLabel: UILabel!
@IBOutlet weak var currentWeatherIcon: UIImageView!
@IBOutlet weak var currentSummaryLabel: UILabel!
@IBOutlet weak var refreshButton: UIButton!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
lazy var forecastAPIClient = ForecastAPIClient(APIKey: "#########") // The API Key is correct, I just hashed it out.
let coordinate = Coordinate(lattitude: 37.8267, longintude: -122.423)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
forecastAPIClient.fetchCurrentWeather(coordinate) {
result in
switch result {
case .Success(let currentWeather):
dispatch_async(dispatch_get_main_queue()) {
self.display(currentWeather)
}
case .Failure(let error as NSError):
dispatch_async(dispatch_get_main_queue()) {
self.showAlert("Unable to retrieve forecast", message: error.localizedDescription)
}
default: break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func display(weather: CurrentWeather) {
currentTemperatureLabel.text = weather.temperatureString
currentPrecipitationLabel.text = weather.percipitationProbabilityString
currentHumidityLabel.text = weather.humidityString
currentSummaryLabel.text = weather.summary
currentWeatherIcon.image = weather.icon
}
func showAlert(title: String, message: String?, style: UIAlertControllerStyle = .Alert) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: style)
let dismissAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(dismissAction)
presentViewController(alertController, animated: true, completion: nil)
}
}
1 Answer
Jacky Chung
10,913 PointsHey there,
I had the same problem and tested each part of the code. I knew the api request part works because I could print out the JSON result.
The problem was in the parse part. In JSON returned from the forecast.io, it looks like this
{
"time":1470382297,
" summary":"Overcast",
"icon":"cloudy",
"nearestStormDistance":15,
"nearestStormBearing":341,
"precipIntensity":0,
"precipProbability":0,
"temperature":55.76,
"apparentTemperature":55.76,
"dewPoint":53.03,
"humidity":0.91,
"windSpeed":6.69,
"windBearing":253,
"visibility":6.45,
"cloudCover":0.98,
"pressure":1012.65,
"ozone":304.24
}
I used precipitationProbability to find the property in my CurrentWeather Decodable. The name in the JSON returned is called precipProbability.
I don't know if you wrote the same thing as mine. Hope it helps!
Brian Uribe
3,488 PointsBrian Uribe
3,488 PointsI'm getting the same error