Hello world! In this post, we’re going to learn how to use the new NSURLSession class to quickly and efficiently connect to the internet and pull data. We’ll be using the OpenWeatherMap API to build a very simple forecasting app. In addition, we’ll also be learning about grand central dispatch (GCD) and how multithreading works in iOS.
Download the source code for this post here.
Learn iOS by building real apps
Check out The Complete iOS Development Course – Build 14 Apps with Swift 2 on Zenva Academy to learn Swift 2 and iOS development from the ground-up with an expert trainer.
BUILD GAMES
FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.
Before we can create the project, we first need to go to OpenWeatherMap and get an API key. For most online API services, developers need to create an account and request an API key. We send this uniquely-generated key whenever we need to use the API to get data from the online API. The identifies the API call with our signature. Anyway, at the website, sign up for a new account. After that, we should see a “My Home” page and the “Setup” tab active. Scroll down and there should be a field called “API key”. We need to write this down or copy it into a text file. We’ll need this unique id after we create the project. Don’t share this API key with anyone! OpenWeatherMap’s API service can cost money, but we’ll just be using their free tier of service. Sharing API keys could cause your usage to spill over into the paid tier!
Let’s create our Single View iOS project and call it WeatherNow. The UI is going to be very simple: we’re just going to position two UITextFields of roughly equal widths right next to each other on the top.Set their keyboard type properties to be decimal pad. We can then position a UIButton right underneath those two UITextFields, centered horizontally. Change the text of the UIButton to say “Get Weather” by double-clicking on the UIButton. Center a UILabel both horizontally and vertically on the screen. Finally, center a UIActivityIndicatorView as well in the same position as the UILabel. This won’t be an issue since only one or neither will be visible at a given time.
Since we’re not covering location services in this post, we’re going to be using the two UITextFields to enter in latitude and longitude coordinates. From there, we can send those latitude and longitude coordinates off to OpenWeatherMap’s API and retrieve weather data back in JSON format.
Regarding autolayout, pin both UITextFields on all sides. Select both of them and, in the pin menu, select equal widths to force the UITextFields to have equal widths. The UILabel should be aligned horizontally and vertically. The UIActivityIndicatorView should also be aligned horizontally and vertically. That should be it for autolayout! Our final layout should look like the screenshot below.
For the code side, create outlets to both UITextFields called latTextField and lonTextField respectively. Also create outlets to the UILabel called weatherLabel and to the UIActivityIndicatorView called networkingIndicator. Finally, create an action from the UIButton called retrieveWeather. Our view controller’s class should look like the following.
class ViewController: UIViewController { @IBOutlet weak var latTextField: UITextField! @IBOutlet weak var lonTextField: UITextField! @IBOutlet weak var weatherLabel: UILabel! @IBOutlet weak var networkingIndicator: UIActivityIndicatorView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } @IBAction func retrieveWeather(_ sender: UIButton) { } }
Before we get started, we should configure the views first. We’ll leave the text in the UILabel for the moment. In the viewDidLoad method, set the hidden property on networkingIndicator to be true. This property, as its name implies, will hide our UIActivityIndicatorView until we’re ready for it.
override func viewDidLoad() { super.viewDidLoad() networkingIndicator.isHidden = true }
Now let’s move on to the UIButton’s action. In this action, we’re going to be connecting to OpenWeatherMap, but we have to make sure the inputs are not empty. After we do that, we can extract the latitude and longitude and convert them to doubles. Then we can unhide our networkingIndicator and start its animation. By Apple’s guidelines, if we’re performing networking requests, we should also animate the UIActivityIndicatorView in the status bar. We can do that with a global property in UIApplication.shared . So far, our retrieveWeather action should look like the following.
@IBAction func retrieveWeather(_ sender: UIButton) { if latTextField.text!.isEmpty || lonTextField.text!.isEmpty { return } let lat = Double(latTextField.text!)! let lon = Double(lonTextField.text!)! networkingIndicator.isHidden = false networkingIndicator.startAnimating() UIApplication.shared.isNetworkActivityIndicatorVisible = true }
Now we can get to formatting the actual request with the parameters. First, we need to format the URL string and convert it to an NSURL object like so.
var urlString = "http://api.openweathermap.org/data/2.5/weather?lat=" + String(lat) + "&lon=" + String(lon) + "&units=imperial" urlString = urlString + "&appid=" + self.APP_ID let url = URL(string: urlString)!
The above code snippet assumes that you have a string constant called APP_ID with your OpenWeatherMap API key. Then we can convert it into a URL and forcibly unwrap the optional, since we’re hardcoding the url in. Feel free to change “imperial” to “metric” if you live anywhere outside the U.S.!
Connecting to the URL and executing the request is intuitive and easy to do.
let task = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in // do something! }) task.resume()
After iOS 7.0, Apple has replaced the old NSURLConnection with the new URLSession object. The URLSession object actually incorporates many different networking classes and allows us to create sessions to better handle our networking tasks. The particular type of session that we’re using is the singleton shared session. A singleton is an object that can only be instantiated once. If we want to get access to it, we would do so through static methods in the object’s class that uses the singleton design pattern. In our case, the singleton shared session is used for the most basic of HTTP requests.
Now we can get to the actual HTTP request. The OpenWeatherMap returns data in JSON format so we need a way to convert from JSON to something useful like a dictionary.
func jsonToDict(_ text: String) -> [String:AnyObject]? { if let data = text.data(using: String.Encoding.utf8) { do { return try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject] } catch _ as NSError { // Do something more useful! } } return nil }
Basically, this code will convert the raw JSON string into a useable dictionary. First, we convert the string into an NSData object using UTF8 encoding. Then, we use the as operator to convert the output into a dictionary in a do-catch clause. The above method will essentially convert the JSON string into a dictionary that we’ll use.
Back to our closure, the first thing we need to do is to convert the data response into a string. We can do that with the similar as operator but force the conversion to a Swift string. Then we can use the method we just created to get a dictionary. Then we can extract the current temperature from the dictionary (technically nested dictionary). We use the same as operator to force it as a double.
let dataStr = NSString(data:data!, encoding:String.Encoding.utf8.rawValue) as! String if let json = self.jsonToDict(dataStr) { let jsonMain = json["main"]! let temp = jsonMain["temp"] as! Double let weatherStr: String = String(temp) DispatchQueue.main.async { self.weatherLabel.text = weatherStr + "ºF" } }
To set the label’s text, we can’t just directly set it in background thread. Recall that this work is accomplished on the background thread so that users don’t experience blocking. Blocking is what happens when the main/UI thread freezes until a piece of code is finished. Networking calls are almost always done on a background thread so users can still interact with the app while the content is loading. This is exactly what URLSession does. The network requests and callbacks occur on a background thread.
There’s a rule that UI elements should only be altered on the UI thread. This prevents many different concurrent threads from changing the same UI element! In our case, we need a mechanism that will allow us to run code on the main thread. For this, we use Grand Central Dispatch (GCD). Using this API, we can execute code on the main thread from a background thread. This is exactly what we do to set the label’s text: call main.async and specify that the block you want to run in the thread you want to run.
Let’s consider another example. We forgot about the UIActivityIndicatorView and the status bar’s network activity indicator! These are both UI elements so we need to use the same method to update the UI in the main/UI thread. However, we want to do this after we’ve set the label’s text if the JSON conversion was successful or not. We can put it after the if-let block to guarantee that those UI elements change, regardless of how the HTTP request went. Here’s the full code for the URLSession closure.
let task = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in let dataStr = NSString(data:data!, encoding:String.Encoding.utf8.rawValue) as! String if let json = self.jsonToDict(dataStr) { let jsonMain = json["main"]! let temp = jsonMain["temp"] as! Double let weatherStr: String = String(temp) DispatchQueue.main.async { self.weatherLabel.text = weatherStr + "ºF" } } DispatchQueue.main.async { self.networkingIndicator.stopAnimating() self.networkingIndicator.isHidden = true UIApplication.shared.isNetworkActivityIndicatorVisible = false } }) task.resume()
The final step is to actually run the task! That’s the final line of code: task.resume() to execute the task in the background thread. Finally, remember to change the text of the UILabel to be empty! Let’s try running our app for the latitude longitude coordinates for New York City.
In this post, covered how to use the new NSURLSession class to pull weather data from OpenWeatherMap’s API. We also discussed grand central dispatch (GCD) and how we should use it to perform UI changes only on the main thread. Ultimately, we built a small, very simple weather forecasting app that told us the current temperature outside!