Yesterday I got really excited :) I’m talking about a feeling when you’re dealing with something which is still new for you, but at the same time you begin to understand how things are organised and what should be done to achieve the goal. I felt like this many years ago when I wrote my first console application in Pascal and when I created my first snake-like game in Delphi. And this pleasant sensation came to me last night ?
I came up with an idea that weather forecast is something that my app needs :) It’s good to know in advance whether it’s going to be rainy, so that you can just stay in bed instead of getting up at 4 am to get the best shot at sunrise ;)
In order to display weather forecast in an app we need a web service that will provide us with the data. I looked for some kind of free weather API and decided for darksky.net. It’s free for up to 1000 api calls per day (don’t think my app will ever reach this limit) and doesn’t have any time frame limits (like they do in open weather api). You get current forecast, forecast for the next minutes, hours and the whole week. You can also specify units, language with weather summary and fine tune what data you want to get back in the result. You have to specify latitude and longitude of the desired location. In order to start using the api you need to set up a free account and generate your api key that will be used in requests. My sample request looks like this:
https://api.darksky.net/forecast/api_key/50.264892,19.023782?exclude=currently,minutely,hourly,alerts,flags&units=si&lang=pl
You can read more about various parameters here. The above api call returns json containing information about weather for the next seven days. Data displayed directly in a browser is hard to analyze so you may want to use some kind of online formatting tool, like this one.
Okay, so my application needs to setup a request to dark sky api, get back the result, parse json and display proper information. There’s a tutorial on Working with JSON in Swift that helped me with this task.
This is the function, that does the whole magic:
static func fetch(lat: Double, lon: Double, completion: @escaping ([String: WeatherModel]) -> Void) { let urlString = global.getWeatherUrl(lat: lat, lon: lon); let url = URL(string: urlString); var days = [String: WeatherModel](); URLSession.shared.dataTask(with:url!) { (data, _, error) in if error != nil { print(error!); } else { do { let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]; let dailyConditions = parsedData["daily"] as! [String:AnyObject]; let daysData = dailyConditions["data"] as! [AnyObject]; for case let dayData in daysData { let time = dayData["time"] as! Double; let date = Date(timeIntervalSince1970: time); let icon = dayData["icon"] as! String; let summary = dayData["summary"] as! String; let tempMin = dayData["temperatureMin"] as! Double; let tempMax = dayData["temperatureMax"] as! Double; days[date.dateOnlyString()] = WeatherModel(date:date, icon:icon, summary: summary, tempMin: tempMin, tempMax:tempMax); } } catch let error as NSError { print(error); } } completion(days); }.resume() }
It takes latitude and longitude as input parameters (to form a proper request) and a completion handler for returning my 7 days weather forecast asynchronously. The dataTask creates a task that retrieves the contents of the specified URL. This is the declaration:
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
Second parameter is interesting: it’s the completion handler to call when the load request is complete. This handler is executed on the delegate queue. If you look at my code you can see that I used a structure that is called a closure in Swift. This block of functionality is like a lambda expression in c#. So when the asynchronous dataTask finishes (either with a result or an error) we get it’s current completion result in data and error variables. Inside the completion handler I parse the result json (it’s all about key – values and array casting) in order to get the weather forecast for the next seven days.
One important thing to know is that you cannot update UI elements from the background thread. You will end up with an error: This application is modifying the autolayout engine from a background thread.
My weather forecast fetch completion event comes from a background thread and is finally raised in ViewController class where if I want to update summary label and set proper weather image. I have to ask DispatchQueue for help:
location!.fetchWeatherForecast { DispatchQueue.main.async(execute: { self.setWeather(); }); }
I’m currently using „summary” field describing whole day conditions, information about the temperature and „icon” field in my application. I’m using VClouds Weather Icons. I added files following the dark sky icon naming convention to my project’s asset catalog. Setting proper image for current weather forecast is now pretty easy:
func getWeatherIcon(_ icon: String) -> UIImage? { let bundle = Bundle(for: type(of: self)); var weatherIcon = UIImage(named: icon, in: bundle, compatibleWith: self.traitCollection); if (weatherIcon == nil) { weatherIcon = UIImage(named: "default", in: bundle, compatibleWith: self.traitCollection); } return weatherIcon; }
My apps’s screen looks now like this:
I will work on my app’s main screen design and auto layout issues in the next days :) Source code is available in magic hours github project.