Instruction
Creating an OpenWeatherMap Account
To make the necessary requests to get data, you’ll first need to understand how to work with the OpenWeatherMap API. Head over to https://openweathermap.org and create an account. To make requests, you’ll need an API key. This will serve as your authorization to request data from this API. Below are the steps necessary to create an account and get your API Key.
- Create an account.
- Once signed in, go to API Keys tab.
- Enter a name for your key, like SwiftyWeather, and click Generate.
Once you’ve generated your API key, head over to the API tab. Once you scroll down, you’ll see a section titled Current Weather Data. Select API Doc to view the docs on interacting with this API. From this page, you can see the various ways you can make a request to this API to get the data you need. The first example shows how to make an API call using three pieces of data:
- Latitude
- Longitude
- API key
For your app, you’ll build a request by city name. The documentation for that request is about halfway down the page. There’s also a link to that section on the right of the page. An example request looks like this:
https://api.openweathermap.org/data/2.5/weather?q={city name}&appid={API key}
Before you make an actual request, you need to understand the different types of requests you can make.
Request Types
When working with RESTful APIs, you’ll encounter four primary HTTP methods: GET, POST, PUT, and DELETE. Each serves a specific purpose in CRUD (Create, Read, Update, Delete) operations. Here’s a basic explanation of each one:
- GET: Retrieves data from the server.
- POST: Sends data to the server to create a new resource.
- PUT: Updates an existing resource on the server.
- DELETE: Deletes a resource from the server.
Below is a code example of how to construct a URLRequest object and set it:
var request = URLRequest(url: url)
request.httpMethod = "GET" // This is where you would set the various method types.
Each method uses the URLRequest object with the httpMethod property set accordingly, the default value is GET. Additional configurations, such as setting headers and the HTTP Body, are made based on the requirements of each method. By understanding these HTTP methods and their usage with URLRequest, you can effectively interact with RESTful APIs in your application. In the weather app tutorial, you’ll only be using the GET method.
URLSession Network Request
To get started, download the materials from the repo and launch the SwiftyWeather project. Upon running the project, you should see a simple app with a search bar and instructions on how to type a city name and press enter. At the moment, this app doesn’t request to get data. You’re going to change that now.
Open the ContentView file. This view represents the first screen you see when running the app. Unpack the components of this view. First, you have the following two variables:
@State private var searchText = ""
@State private var errorMessage: String?
The first state variable, searchText, contains the text that the user enters when typing in the search bar. The second, errorMessage, variable will contain error messages you want to display on the screen. Both variables are decorated with @State property wrappers, which allow SwiftUI to refresh your screen whenever the data changes.
var body: some View {
NavigationStack {
VStack {
ContentUnavailableView(
errorMessage ?? "Please type a city above and press enter",
systemImage: "magnifyingglass"
)
}
}
.searchable(text: $searchText, prompt: Text("City Name"))
.onSubmit(of: .search) {
}
}
This code contains the logic to display a screen with a search bar at the top and a message in the center of the screen. For now, focus on the following modifier:
.onSubmit(of: .search) {
// here, you will fetch the weather data
}
This code will be executed whenever the user presses the search button on the keyboard. This is where you’ll enter the temporary code to fetch the weather data. First, you need the endpoint from the OpenWeatherMap API. You’ve already seen an example of this from above.
Add the following code in between the curly braces of the onSubmit modifier:
let apiKey = "YOUR API KEY HERE"
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(searchText)&appid=\(apiKey)") else {
print("Invalid URL")
return
}
The first constant, apiKey, is where you’ll store the API key you generated from the OpenWeatherMap page.
Replace the YOUR API KEY HERE text with a valid value from the page.
The second constant, url, contains the URL to which you’ll make your request. Notice that you use string interpolation to inject your search text containing your city name and the API key into the URL. This code will generate an optional URL object for you; however, you want to ensure it’s non-optional. To achieve this, you use a guard statement to safely unwrap the optional and print any error to the console.
Under that code, create a URLSession object. URLSession is the class responsible for making network requests in iOS. You can use a shared instance for simple requests. Add the following code under the url constant:
let session = URLSession.shared
Once you have your session object, you can create a URLSessionDataTask to perform the fetch. Add the following code underneath the session constant:
let task = session.dataTask(with: url) { data, response, error in
// Handle response here
}
task.resume()
URLSessionDataTask is used to fetch the data you need. You can create it using the dataTask(with:) method, passing in a URL or URLRequest. In your case, you provide the url created above.
When the request is complete, you’ll be provided with three parameters:
-
Data: The
datarepresents the raw data received from the network request. This data is typically in the form of bytes and needs to be processed to be useful. You’ll learn more about parsing this data in lesson 3. -
Response: The
responseobject contains metadata about the response, such as the status code. This value is typically cast as anHTTPURLResponseto access specific HTTP response information. -
Error: The
errorobject contains information about any error during the request. If an error occurs, this variable will contain the error details. All three parameters are optional.
If the request fails due to no internet connection, the data and response parameters will be nil, and the error parameter will contain the network failure message. In a successful request, the error parameter will be nil, and the data and response parameters will contain the information from the server.
The last step is to call resume on the task, which starts it.
Handling Errors
Error handling is an essential part of any application, especially one that makes network requests. Several types of errors can occur when requesting information from a server. Some errors are procedural, meaning there is a problem with how you made the request. Other errors are functional and can stem from how you process or parse a request.
In the section above, you wrote the code to make a request. However, you aren’t handling the data, response, or errors. You’ll address that now.
In the body of your task, the first parameter you’ll handle is error. You’ll check for the error first before you complete any of the other work.
Add the following code in the body of the data task:
if let error {
print("Error: \(error.localizedDescription)")
return
}
This code checks for an error by attempting to unwrap the optional error parameter. If it’s successful, it means that there’s an error. For now, you’ll print this error to the console. In the next lesson, you’ll build a more concrete solution using our NetworkError enum. Once you’ve printed the error, you call return to stop the execution of any remaining code.
Once you’ve addressed the error case, you can move on to handling the response parameter. Generally speaking, a good status code from a server is between 200 and 300. For now, if the status is outside of this range, you’ll print a message to the console. It’s time to add some code to validate that the request made returns a status code within this range:
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
print("Invalid Response")
return
}
The last step is to work with the data you received from the server. This parameter will contain the weather data you seek to make your app work. Add the following code under the response code:
guard let data else {
print("No data received")
return
}
print(String(data: data, encoding: .utf8) ?? "")
In the code above, you safely unwrap the data parameter to ensure you have something to work with. If data is nil, you print a simple error message to the console. Once you have validated that there’s data, you’ll convert the raw data into a string by using the String(data:encoding:) initializer. That’s the last line of code you’ll write for this lesson. The entire onSubmit modifier should look like this:
.onSubmit(of: .search) {
let apiKey = "YOUR API KEY HERE"
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(searchText)&appid=\(apiKey)") else {
print("Invalid URL")
return
}
let session = URLSession.shared
let task = session.dataTask(with: url) { data, response, error in
if let error {
print("Error: \(error.localizedDescription)")
return
}
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
print("Invalid Response")
return
}
guard let data else {
print("No data received")
return
}
print(String(data: data, encoding: .utf8) ?? "")
}
task.resume()
}
Now, the moment you’ve been waiting for!
Build and run your application to the simulator. Once the app runs, enter a city name in the search bar and press search. You should see some JSON data printed in the console if everything works correctly. This is the current weather data! In lesson 3, you’ll learn how to properly decode or parse this data so you can display it correctly in your UI. For now, the data shown in the console proves that everything is hooked up properly to request and process data from the server.