In this tutorial we will build an iOS application that will fetch the weather data from an online weather API and display it to the user. The App will display the country name, temperature, weather state, current date, and an image showing the current weather state.
Let’s start by creating a new Xcode project.
This project uses Alamofire framework to create the GET request and fetch the data needed from the weather API.
We will install that framework using Cocoapods.
How to install Cocoapods:
1- Open terminal and type:
1 |
sudo gem install cocoapods |
Gem will get installed in Ruby inside System library. Or try on 10.11 Mac OSX El Capitan, type:
1 |
sudo gem install -n /usr/local/bin cocoapods |
If there is an error “activesupport requires Ruby version >= 2.xx”, then install latest activesupport first by typing in terminal.
1 |
sudo gem install activesupport -v 4.2.6 |
2-Â After installation, there will be a lot of messages, read them and if no error found, it means cocoapods installation is done. Next, you need to setup the cocoapods master repo. Type in terminal:
1 |
pod setup |
And wait it will download the master repo. The size is very big (300MB++ at Sept 2016). So it can be a while. You can track of the download by opening Activity and goto Network tab and search for git-remote-https. Alternatively you can try adding verbose to the command like so:
1 |
pod setup --verbose |
3-Â Once done it will output “Setup Complete”, and you can create your XCode project and save it.
Installing Alamofire 4:
First, open the Terminal window (on MAC) and browse to the directory of the Xcode project we created. I saved my project on the Desktop. So firs thing I’m going to do is “cd” to the Desktop. Then I’m going to “cd” to the project folder:
Next we need to do is create the Pod file. This file will be used by Cocoapods to run the installation of the Pods we specify for the project. In the terminal window type:
1 |
pod init |
Next, go to the project folder and edit the “Podfile” using a text editor:
1 2 3 4 5 6 7 8 9 10 |
# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'SYSteenWeatherApp' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for SYSteenWeatherApp end |
Replace everything with the below:
1 2 3 4 5 6 7 |
platform :ios, '10.0' use_frameworks! target 'SYSteenWeatherApp' do pod 'Alamofire', '~> 4.0' end |
The last step is to go back to the Terminal window and type : “pod install” to install the Pods required:
Now that Alamofire is installed, close Xcode and then browse to the project folder and run the file “SYSteenWeatherApp.xcworkspace” . From now on, we will open the project using the .xcworkspace extension to load the Pods.
Connect UIViews to ViewController:
Now we are ready to start designing the View Controller.
Go ahead and place those UIViews that will represent the following:
- Image View as a blue background
- Image View to show weather state
- 1st Label: Today’s date.
- 2nd Label: Current temperature
- 3rd Label: Location
- 4th Label: Weather state
You can mess with the look and feel of those views but this is what I got at the end. You can fill the labels with some dummy text for now.
In order to get some nice weather icons, I chose http://garmahis.com/weather-icons-free-vector-weather-icon-pack/
Unpack the file and import the images to Xcode Assets.
Note: Rename the images to whatever status it shows. For example, the sun icon will symbol the clear sky status, so rename it to Clear. You will find out why later. Trust me.
Head over to the ViewController.swift file and let’s connect those UILabels and UIImageViews. Your file should look like this now after connecting the views:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var dateLabel: UILabel! @IBOutlet weak var tempLabel: UILabel! @IBOutlet weak var locationLabel: UILabel! @IBOutlet weak var weatherImage: UIImageView! @IBOutlet weak var weatherLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() } } |
Create the Data Model:
In order to correctly follow the MVC standard in our design, we will create a new swift file to manage our data model.
Create a new Swift file and add it to your project. Name it DataModel.swift.
In our DataModel file, we will use four variables to build our view. Open that file and add the following:
1 2 3 4 5 6 7 8 9 |
class DataModel { private var _date: Double? private var _temp: String? private var _location: String? private var _weather: String? typealias JSONStandard = Dictionary<String, AnyObject> } |
We notice that we chose _date to be a Double and not of type Date. The reason behind this is that the JSON data that we fetch has the date in EPOCH linux time which is a double. Fortunately, Swift has a function for us to do the conversion.
typealias is used to make our code easier to read. So instead of writing  Dictionary<String, AnyObject> all over the place, we can simplify it with JSONStandard
Next, we will validate those values just in case we get any “nil” when downloading the JSON file, or in case we weren’t able to download any file.
Add the following to the DataModel file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import Alamofire class DataModel { private var _date: Double? private var _temp: String? private var _location: String? private var _weather: String? typealias JSONStandard = Dictionary<String, AnyObject> let url = URL(string: "http://api.openweathermap.org/data/2.5/weather?q=Portland&appid=a7bbbd5e82c675f805e7ae084f742024")! var date: String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long dateFormatter.timeStyle = .none let date = Date(timeIntervalSince1970: _date!) return (_date != nil) ? "Today, \(dateFormatter.string(from: date))" : "Date Invalid" } var temp: String { return _temp ?? "0 °C" } var location: String { return _location ?? "Location Invalid" } var weather: String { return _weather ?? "Weather Invalid" } } |
For the variables _temp, _location, _weather, we return an “Invalid” string in case their values are nil. As for the date, we are going to return a nil in case the date is not valid, and also will return a formatted date in case the date was valid, using the DateFormatter.
Notice how we convert the date from linux time to Date() using the function timeIntervalSince1970.
Our DataModel is not ready yet. Now we need to define the URL that we will use to fetch the JSON data from.
I used the openweathermap.org Weather API. Go to that website and Sign up. Once you sign up, go to your profile and write down your API key. You will use that key to connect to the OpenWeather API.
You will have to use the following URL, with some slight changes, to fetch the needed JSON data:
1 |
http://api.openweathermap.org/data/2.5/weather?q=Portland&appid=a7bbbd5e82c675f805e7ae084f742024 |
You can replace your APPID in the above link. In addition, feel free to choose the location you want. Notice the bold text above.
Head over to DataModel.swift and add the URL variable:
1 2 3 4 5 6 7 8 9 10 |
class DataModel { private var _weather: String! .... let url = URL(string: "http://api.openweathermap.org/data/2.5/weather?q=Portland&appid=a7bbbd5e82c675f805e7ae084f742024")! var date: String { let dateFormatter = DateFormatter() .... |
We converted that string into a URL in order to use with Alamofire.
Fetching using Alamofire 4:
Our URL is ready. Now we will create a GET request using Alamofire to get the JSON data. Add the following function to your DataModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func downloadData(completed: @escaping ()-> ()) { Alamofire.request(url).responseJSON(completionHandler: { response in let result = response.result if let dict = result.value as? JSONStandard, let main = dict["main"] as? JSONStandard, let temp = main["temp"] as? Double, let weatherArray = dict["weather"] as? [JSONStandard], let weather = weatherArray[0]["main"] as? String, let name = dict["name"] as? String, let sys = dict["sys"] as? JSONStandard, let country = sys["country"] as? String, let dt = dict["dt"] as? Double { self._temp = String(format: "%.0f °C", temp - 273.15) self._weather = weather self._location = "\(name), \(country)" self._date = dt } completed() }) } |
I know this is alot of code but trust me it works!
Alamofire.request is used to make a GET request to fetch the required data. The response we get is in a completion handler.
To make things clear for you, find below our JSON data we fetched:
1 |
if let dict = result.value as? Dictionary<String, AnyObject> |
This states that the fetched JSON file will be a dictionary of key of type String and value of AnyObject.
1 |
if let main = dict["main"] as? Dictionary<String, AnyObject> |
We use this line to fetch the “main” dictionary of keys and values.
To fetch the temperature, we use the following
1 2 3 |
if let temp = main["temp"] as? Double { self._temp = String(format: "%.0f °C", temp - 273.15) } |
The reason why we subtracted 273.15 from the fetched temperature is to convert it from Kelvins to Celsius.
Finally, we are done with our DataModel. Time to head over to the ViewController.swift.
Finalizing the View Controller:
Inside ViewController file, add the variable weather of type DataModel:
1 2 3 4 5 6 7 |
@IBOutlet weak var weatherLabel: UILabel! ..... var weather = DataModel() override func viewDidLoad() { super.viewDidLoad() ..... |
Create a new function inside ViewController, name it updateUI() . We will use that function to update the UIViews once the fetching of data is complete:
1 2 3 4 5 6 7 |
func updateUI() { dateLabel.text = weather.date tempLabel.text = "\(weather.temp)" locationLabel.text = weather.location weatherLabel.text = weather.weather weatherImage.image = UIImage(named: weather.weather) } |
Notice the text in bold. We will let Xcode choose the image based on the weather state we fetched. So a “Clear” state shall give us the image Clear (Now you know why I asked you to rename the images based on their state).
The last step is to add the downloadData() function into the ViewDidLoad method and initialize the weather object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var dateLabel: UILabel! @IBOutlet weak var tempLabel: UILabel! @IBOutlet weak var locationLabel: UILabel! @IBOutlet weak var weatherImage: UIImageView! @IBOutlet weak var weatherLabel: UILabel! var weather = DataModel() override func viewDidLoad() { super.viewDidLoad() weather.downloadData { self.updateUI() } } func updateUI() { dateLabel.text = weather.date tempLabel.text = "\(weather.temp)" locationLabel.text = weather.location weatherLabel.text = weather.weather weatherImage.image = UIImage(named: weather.weather) } } |
Thats it! Run the project. You will notice how the data will change based on the JSON file we fetched. Check the output below:
Hi great tutorial. What font has you used? thx
Hey zhenja thanks!
font name is Arial Rounded MT Bold
Cheers!
I’m gonna try this out tonight. Thank you and I’m glad I found your site
If I’ve done pod setup before for another project do I have to do that step again for this project?
Just need a clarification. If I’ve done pod setup before for another project do I have to do that step again for this project?
yes
Hi,
I’m having one problem.
The updated data is not displaying on the app screen. It displays on the output section but not on the app screen. How to fix it?
I need to see the project