Swift applications can retrieve historical weather, current conditions, and forecasts directly from the Visual Crossing Timeline Weather API.
In this tutorial, we’ll use Swift’s built-in URLSession networking support and Codable JSON decoding to retrieve weather data and convert the API response into strongly typed Swift structures.
The Visual Crossing Weather API provides programmatic access to historical, current, and forecast weather information. If you want to explore or download weather datasets before building an application, see Visual Crossing Weather Data.
To follow the examples, sign up for a free Visual Crossing account and obtain your Weather API key.
For the complete request and response reference, see the Timeline Weather API documentation.
What we’ll build
We’ll create a simple Swift weather client that:
- Builds a Timeline Weather API URL.
- Retrieves weather data using
URLSession. - Checks the HTTP response for errors.
- Decodes the returned JSON using
JSONDecoder. - Reads daily forecast values.
- Retrieves historical weather for a date range.
- Adds current conditions and hourly weather.
The same Timeline Weather API supports historical and forecast data, so the application does not need separate APIs for each type of weather request.
Get your Weather API key
Your Visual Crossing Weather API key authenticates Weather API requests.
For production applications, avoid placing credentials directly in source files that may be committed to a repository.
For a simple example, we’ll read the key from the application’s environment:
guard let apiKey =
ProcessInfo.processInfo.environment[
"VISUAL_CROSSING_API_KEY"
],
!apiKey.isEmpty
else {
fatalError(
"VISUAL_CROSSING_API_KEY is not set"
)
}
For an iOS or macOS application, you may instead load configuration from an appropriate application configuration or secret-management mechanism.
Visual Crossing plans include usage and request limits, so applications should be designed to operate within the limits of the account plan being used.
Understanding the Timeline Weather API URL
The Timeline Weather API uses this basic path structure:
/timeline/[location]/[date1]/[date2]
Only the location is required.
A forecast request for Paris can use:
/timeline/Paris%2C%20France
A request for a single historical date can use:
/timeline/Paris%2C%20France/2026-07-01
A historical date range can use:
/timeline/Paris%2C%20France/2026-07-01/2026-07-07
When dates are omitted, the Timeline Weather API returns the available forecast data. When historical dates are supplied, it returns weather data for those dates.
Define the weather response structures
Swift’s Codable support allows the JSON response to be decoded directly into Swift types.
For a simple daily-weather example, define:
struct WeatherResponse: Decodable {
let resolvedAddress: String
let timezone: String?
let days: [WeatherDay]
}
struct WeatherDay: Decodable {
let datetime: String
let tempmax: Double?
let tempmin: Double?
let precip: Double?
let precipprob: Double?
let conditions: String?
}
We only need to define the JSON fields our application uses. JSONDecoder ignores additional fields returned by the Weather API.
Notice that temperature and precipitation values use numeric Swift types rather than strings.
Build the Weather API URL
Swift’s URLComponents and URLQueryItem types provide a convenient way to create a request without manually concatenating query parameters.
func buildWeatherURL(
location: String,
apiKey: String,
startDate: String? = nil,
endDate: String? = nil
) throws -> URL {
var path =
"https://weather.visualcrossing.com/" +
"VisualCrossingWebServices/rest/services/" +
"timeline/"
guard let encodedLocation =
location.addingPercentEncoding(
withAllowedCharacters:
.urlPathAllowed
)
else {
throw WeatherError.invalidURL
}
path += encodedLocation
if let startDate {
path += "/" + startDate
}
if let endDate {
path += "/" + endDate
}
guard var components =
URLComponents(string: path)
else {
throw WeatherError.invalidURL
}
components.queryItems = [
URLQueryItem(
name: "key",
value: apiKey
),
URLQueryItem(
name: "unitGroup",
value: "metric"
),
URLQueryItem(
name: "include",
value: "days"
),
URLQueryItem(
name: "elements",
value:
"datetime,tempmax,tempmin," +
"precip,precipprob,conditions"
),
URLQueryItem(
name: "contentType",
value: "json"
)
]
guard let url = components.url
else {
throw WeatherError.invalidURL
}
return url
}
Using URLQueryItem avoids manually escaping API query parameters.
The include parameter requests only daily weather, while elements limits the response to the fields used by the example.
Define application errors
Create a small error type for URL and HTTP failures:
enum WeatherError: Error {
case invalidURL
case invalidResponse
case apiError(
statusCode: Int,
message: String
)
}
This makes it easier for the application to distinguish request-building errors from Weather API errors.
Retrieve weather using URLSession
Modern Swift supports async/await with URLSession.
Create this function:
func getWeather(
location: String,
apiKey: String,
startDate: String? = nil,
endDate: String? = nil
) async throws -> WeatherResponse {
let url = try buildWeatherURL(
location: location,
apiKey: apiKey,
startDate: startDate,
endDate: endDate
)
let (data, response) =
try await URLSession.shared.data(
from: url
)
guard let httpResponse =
response as? HTTPURLResponse
else {
throw WeatherError.invalidResponse
}
guard (200...299).contains(
httpResponse.statusCode
)
else {
let message =
String(
data: data,
encoding: .utf8
) ?? "Unknown Weather API error"
throw WeatherError.apiError(
statusCode:
httpResponse.statusCode,
message: message
)
}
return try JSONDecoder().decode(
WeatherResponse.self,
from: data
)
}
The core request is:
let (data, response) =
try await URLSession.shared.data(
from: url
)
We then verify that the response is an HTTP response and that its status code indicates success before trying to decode the weather data.
Retrieve a weather forecast
You can now retrieve weather for any supported location.
do {
let weather =
try await getWeather(
location: "Paris, France",
apiKey: apiKey
)
print(
"Weather for " +
weather.resolvedAddress
)
for day in weather.days {
print(
"\(day.datetime): " +
"\(day.conditions ?? ""), " +
"high \(day.tempmax ?? 0), " +
"low \(day.tempmin ?? 0)"
)
}
} catch {
print(
"Unable to retrieve weather: " +
"\(error)"
)
}
Daily weather records are available in:
weather.days
Each WeatherDay can contain values such as:
day.datetime
day.tempmax
day.tempmin
day.precip
day.precipprob
day.conditions
Complete Swift forecast example
The following example combines the main pieces:
import Foundation
enum WeatherError: Error {
case invalidURL
case invalidResponse
case apiError(
statusCode: Int,
message: String
)
}
struct WeatherResponse: Decodable {
let resolvedAddress: String
let timezone: String?
let days: [WeatherDay]
}
struct WeatherDay: Decodable {
let datetime: String
let tempmax: Double?
let tempmin: Double?
let precip: Double?
let precipprob: Double?
let conditions: String?
}
func buildWeatherURL(
location: String,
apiKey: String,
startDate: String? = nil,
endDate: String? = nil
) throws -> URL {
var path =
"https://weather.visualcrossing.com/" +
"VisualCrossingWebServices/rest/services/" +
"timeline/"
guard let encodedLocation =
location.addingPercentEncoding(
withAllowedCharacters:
.urlPathAllowed
)
else {
throw WeatherError.invalidURL
}
path += encodedLocation
if let startDate {
path += "/" + startDate
}
if let endDate {
path += "/" + endDate
}
guard var components =
URLComponents(string: path)
else {
throw WeatherError.invalidURL
}
components.queryItems = [
URLQueryItem(
name: "key",
value: apiKey
),
URLQueryItem(
name: "unitGroup",
value: "metric"
),
URLQueryItem(
name: "include",
value: "days"
),
URLQueryItem(
name: "elements",
value:
"datetime,tempmax,tempmin," +
"precip,precipprob,conditions"
),
URLQueryItem(
name: "contentType",
value: "json"
)
]
guard let url = components.url
else {
throw WeatherError.invalidURL
}
return url
}
func getWeather(
location: String,
apiKey: String,
startDate: String? = nil,
endDate: String? = nil
) async throws -> WeatherResponse {
let url = try buildWeatherURL(
location: location,
apiKey: apiKey,
startDate: startDate,
endDate: endDate
)
let (data, response) =
try await URLSession.shared.data(
from: url
)
guard let httpResponse =
response as? HTTPURLResponse
else {
throw WeatherError.invalidResponse
}
guard (200...299).contains(
httpResponse.statusCode
)
else {
let message =
String(
data: data,
encoding: .utf8
) ?? "Unknown Weather API error"
throw WeatherError.apiError(
statusCode:
httpResponse.statusCode,
message: message
)
}
return try JSONDecoder().decode(
WeatherResponse.self,
from: data
)
}
guard let apiKey =
ProcessInfo.processInfo.environment[
"VISUAL_CROSSING_API_KEY"
],
!apiKey.isEmpty
else {
fatalError(
"VISUAL_CROSSING_API_KEY is not set"
)
}
do {
let weather =
try await getWeather(
location: "Paris, France",
apiKey: apiKey
)
print(
"Weather for " +
weather.resolvedAddress
)
for day in weather.days {
print(
"\(day.datetime): " +
"\(day.conditions ?? ""), " +
"high \(day.tempmax ?? 0), " +
"low \(day.tempmin ?? 0), " +
"precipitation probability " +
"\(day.precipprob ?? 0)%"
)
}
} catch {
print(
"Unable to retrieve weather: " +
"\(error)"
)
}
Retrieve historical weather
The same Swift function can retrieve historical weather by supplying dates.
For a single date:
let weather =
try await getWeather(
location: "Paris, France",
apiKey: apiKey,
startDate: "2026-07-01"
)
For a date range:
let weather =
try await getWeather(
location: "Paris, France",
apiKey: apiKey,
startDate: "2026-07-01",
endDate: "2026-07-07"
)
The response still uses the same days array, so your decoding and processing code does not need to change.
If you primarily need to explore or download historical datasets rather than retrieve them from application code, see Visual Crossing Weather Data.
Add current conditions
To retrieve current weather conditions, change the API request from:
URLQueryItem(
name: "include",
value: "days"
)
to:
URLQueryItem(
name: "include",
value: "current,days"
)
Then add a current-conditions structure:
struct CurrentConditions: Decodable {
let temp: Double?
let humidity: Double?
let conditions: String?
}
and update WeatherResponse:
struct WeatherResponse: Decodable {
let resolvedAddress: String
let timezone: String?
let currentConditions:
CurrentConditions?
let days: [WeatherDay]
}
Current conditions can then be accessed using:
weather.currentConditions?.temp
weather.currentConditions?.humidity
weather.currentConditions?.conditions
Retrieve hourly weather
To retrieve hourly data, request:
include=days,hours
Then define an hourly structure:
struct WeatherHour: Decodable {
let datetime: String
let temp: Double?
let precipprob: Double?
let conditions: String?
}
and add an hours property to WeatherDay:
struct WeatherDay: Decodable {
let datetime: String
let tempmax: Double?
let tempmin: Double?
let precip: Double?
let precipprob: Double?
let conditions: String?
let hours: [WeatherHour]?
}
You can then process hourly weather:
if let hours =
weather.days.first?.hours {
for hour in hours {
print(
"\(hour.datetime): " +
"\(hour.temp ?? 0), " +
"\(hour.conditions ?? "")"
)
}
}
Request only the weather elements you need
The Timeline Weather API supports many different weather fields.
Use the elements parameter to limit the response to those required by your Swift application.
For example:
URLQueryItem(
name: "elements",
value:
"datetime,tempmax,tempmin,conditions"
)
is sufficient for a basic daily forecast.
Other available weather data includes fields such as:
temp
feelslike
humidity
dew
precip
precipprob
snow
snowdepth
windspeed
windgust
winddir
pressure
cloudcover
visibility
solarradiation
solarenergy
uvindex
conditions
icon
For the complete list, see the Timeline Weather API documentation.
Use US or metric units
The examples use metric units:
URLQueryItem(
name: "unitGroup",
value: "metric"
)
For US units, change this to:
URLQueryItem(
name: "unitGroup",
value: "us"
)
The selected unit group controls units including temperature, precipitation, wind speed, and visibility.
Handle errors in your application
Weather API requests may fail because of:
- Invalid API keys
- Invalid locations
- Invalid dates
- Invalid request parameters
- Account usage limits
- Network failures
- Connection timeouts
The getWeather() example checks the HTTP status before attempting to decode the response.
When an error occurs, it preserves both the status code and returned response body:
throw WeatherError.apiError(
statusCode:
httpResponse.statusCode,
message: message
)
This information can be logged or displayed appropriately by your application.
Using the Weather API in iOS and macOS apps
The networking and decoding code in this tutorial can be used in iOS, iPadOS, and macOS applications.
For example, an application could retrieve weather in a view model:
@MainActor
final class WeatherViewModel:
ObservableObject {
@Published
var weather: WeatherResponse?
@Published
var errorMessage: String?
func loadWeather(
location: String,
apiKey: String
) async {
do {
weather =
try await getWeather(
location: location,
apiKey: apiKey
)
} catch {
errorMessage =
error.localizedDescription
}
}
}
The resulting weather object can then be displayed using SwiftUI, UIKit, AppKit, or another Apple UI framework.
The Weather API retrieval layer remains independent of the interface used by the application.
API keys in distributed mobile applications
API keys require special consideration in iOS and other client applications.
Code and configuration distributed with an application can potentially be inspected by users. An API key embedded directly inside an app should therefore not automatically be considered secret simply because it is compiled into the application.
For applications where the API key must remain private, consider an architecture in which your server handles authenticated Weather API requests and your application communicates with your own backend.
The appropriate design depends on your application, deployment model, and account requirements.
Going further
Once your Swift application can retrieve Timeline Weather API data, the same approach can support:
- Historical weather
- Current conditions
- Hourly forecasts
- Daily forecasts
- Weather alerts
- Mobile weather applications
- Travel applications
- Agricultural applications
- Energy applications
- Business and operational tools
The Visual Crossing Weather API provides programmatic access to historical weather, current conditions, forecasts, alerts, and additional weather information.
For interactive access to historical and forecast datasets, see Visual Crossing Weather Data.
If you haven’t created an account yet, sign up for a free Visual Crossing account.
For detailed request options and response fields, see the Timeline Weather API documentation.
Summary
Modern Swift includes everything needed to retrieve and process Visual Crossing Weather API data.
The basic process is:
- Obtain a Visual Crossing Weather API key.
- Build a Timeline Weather API URL using
URLComponents. - Send the request using
URLSession. - Use
async/awaitfor asynchronous networking. - Check the HTTP status before decoding the response.
- Decode the JSON using
JSONDecoderandCodable. - Read daily, hourly, or current weather values from strongly typed Swift structures.
- Use the resulting data in SwiftUI, UIKit, AppKit, or other Swift applications.
Because the Timeline Weather API uses a consistent interface for historical, current, and forecast weather, the same Swift integration can support a wide variety of Apple-platform applications.

