WeatherKit Tutorial: Getting Started
The tutorial covers exploring WeatherKit, displaying local weather forecasts and using Swift Charts for detailed predictions across locations. By Saleh Albuga.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
WeatherKit Tutorial: Getting Started
25 mins
- Getting Started
- Setting Up Your Project
- Registering App Identifiers
- Activating WeatherKit Capability
- Using WeatherService
- Displaying the Current Weather Forecast
- Exploring the Weather Data
- Getting a More Detailed Forecast
- 10-Day Weather Overview
- Presenting the Hourly Overview
- Adding a Daily Temperature Chart
- Adding an Hourly Temperature Chart
- Preparing the App for Distribution
- Where to Go From Here?
Exploring the Weather Data
Many of the forecast measurements from CurrentWeather
have been presented, along with their units:
- The weather condition icon and text description.
- The temperature and feels-like temperature.
- Humidity.
- Wind speed.
- UV index.
WeatherKit returns different data types for measurements. Some measurements, like humidity
, are of type Double and are used directly. KodecoWeather multiplies the returned value, which ranges from 0.0 to 1.0, by 100 to convert it to a percentage:
Text("Humidity: \((current.humidity * 100)
.formatted(.number.precision(.fractionLength(1))))%")
But other measurements, such as temperature
, wind
and uvIndex
are of type Measurement
, and KodecoWeather uses them differently. Measurement
contains more properties about the parameter it represents, including metadata and the unit of measurement. As an example, look at the code for the temperature
measurement:
let tUnit = current.temperature.unit.symbol
Text("\(current.temperature.value.formatted(
.number.precision(.fractionLength(1))))\(tUnit)")
.font(Font.system(.title))
KodecoWeather accesses the temperature value from temperature.value
and its unit symbol from temperature.unit.symbol
. It also formats the value to display only one decimal place, using BinaryFloatingPoint.formatted(_:)
.
Measurement
offers methods that simplify working with measurements. These methods help convert between units of measurement, format them and perform operations on them.
WeatherKit returns the weather condition icon using an SF Symbol, eliminating the need for custom icons. All weather conditions have symbols in the SF Symbols. In this view, that symbol is displayed in an Image
:
Image(systemName: current.symbolName)
.font(.system(size: 75.0, weight: .bold))
WeatherKit provides more than just the basic weather measurements seen in the CurrentWeather
query.
For instance, when comparing DayWeather
to CurrentWeather
, additional measurements that are calculated daily, such as highTemperature
, lowTemperature
, rainfallAmount
and snowfallAmount
, are found in DayWeather
. Celestial parameters related to the moon and sun events, like moonPhase
, sunRise
, sunSet
and many others, are also included. These parameters are useful in domains like agriculture and astronomy.
You can check all the measurements provided by the different models in Apple’s documentation:
In the next section, you’ll implement a view that displays a detailed weather forecast.
Getting a More Detailed Forecast
Go back to the app and switch to the Detailed tab. You’ll see a list of predefined locations.
Tap a city. You’ll see an empty view with the city name and “Detailed forecast” label. You’ll implement the view right now!
First, open DetailedWeatherView.swift and find the following definitions:
var location: Location
var weatherServiceHelper = WeatherData.shared
location
stores the selected location, passed from the LocationsListView
view. weatherServiceHelper
contains the singleton from WeatherData
, as you observed in CurrentWeatherView
.
Below this code, add the following variables:
@State var dailyForecast: Forecast?
@State var hourlyForecast: Forecast?
These will be used as follows:
-
dailyForecast
will provide a 10-day forecast starting today. -
hourlyForecast
will offer a 25-hour forecast from the current hour.
Next, within the task(priority:_:)
block:
.task {
// request weather forecast here
}
Replace the comment with the following code:
isLoading = true
Task.detached {
dailyForecast = await weatherServiceHelper.dailyForecast(
for: CLLocation(
latitude: location.latitude,
longitude: location.longitude
)
)
hourlyForecast = await weatherServiceHelper.hourlyForecast(
for: CLLocation(
latitude: location.latitude,
longitude: location.longitude
)
)
isLoading = false
}
Here, you display a ProgressView
by setting isLoading
to true, like your approach in CurrentWeatherView
.
Next, you request the daily and hourly forecasts for your chosen location asynchronously. The methods dailyForecast(for:)
and hourlyForecast(for:)
require a CLLocation
type for the location. So you create a CLLocation
, using the latitude and longitude from the location
variable.
You’ll showcase the forecasts in the upcoming sections.
10-Day Weather Overview
Using the data in dailyForecast
, you’ll display:
- A horizontal list with the weather condition icon, sunrise, sunset, moonrise and moonset times for each day.
- A horizontal list indicating the moon phase for each day.
Beginning with the first list, open DayDetailsCell.swift. This list cell view displays the day’s details mentioned above.
At the start of the view struct, add:
var dayWeather: DayWeather?
This variable receives the DayWeather
from the list iterator you’ll set up soon. In the view body
, replace the existing code with:
if let day = dayWeather {
VStack {
Text(day.date.formatted(.dateTime.day().month()))
.font(.system(size: 15.0))
Divider()
Image(systemName: day.symbolName)
.font(.system(size: 25.0, weight: .bold))
.padding(.bottom, 3.0)
HStack {
VStack {
Image(systemName: "sunrise")
.font(.system(size: 12.0, weight: .bold))
.foregroundColor(.orange)
Text(day.sun.sunrise?.formatted(.dateTime.hour().minute()) ?? "?")
.font(.system(size: 12.0))
}
VStack {
Image(systemName: "sunset")
.font(.system(size: 12.0, weight: .bold))
.foregroundColor(.orange)
Text(day.sun.sunset?.formatted(.dateTime.hour().minute()) ?? "?")
.font(.system(size: 12.0))
}
}
Divider()
}
}
Examine the code. If dayWeather
exists, it displays:
- The date, e.g., Jul 22.
- The weather condition icon using an SF symbol.
- Sunrise and sunset times, accompanied by their SF symbols.
After the last Divider()
, insert:
HStack {
VStack {
Image(systemName: "moon.circle")
.font(.system(size: 13.0, weight: .bold))
.foregroundColor(.indigo)
Text(day.moon.moonrise?.formatted(.dateTime.hour().minute()) ?? "?")
.font(.system(size: 12.0))
}
VStack {
Image(systemName: "moon.haze.circle")
.font(.system(size: 13.0, weight: .bold))
.foregroundColor(.indigo)
Text(day.moon.moonset?.formatted(.dateTime.hour().minute()) ?? "?")
.font(.system(size: 12.0))
}
}
This section displays the moonrise and moonset times, mirroring the sunrise and sunset layout.
sunset
and moonset
, might not always be available. In such cases, the view defaults to “?” instead of leaving it blank.The day details view is ready! Open DetailedWeatherView.swift and start implementing the list.
Find this comment in the code:
// present the data
Replace it with:
if let daily = dailyForecast {
Text("10-day overview").font(Font.system(.title))
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(daily, id: \.date) { day in
DayDetailsCell(dayWeather: day)
Divider()
}
}
.frame(height: 150.0)
}
.padding(.all, 5)
Divider()
}
Here, you add the header “10-day overview”. Inside a horizontal ScrollView
, an HStack
iterates over the days from dailyForecast
. A DayDetailsCell
displays each day’s information. Simple, right?
Next, add the moon phases list. Insert the following after the last Divider()
line, within the if scope:
Text("moon phases").font(Font.system(.title3))
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(daily, id: \.date) { day in
Image(systemName: day.moon.phase.symbolName)
.font(.system(size: 25.0, weight: .bold))
.padding(.all, 3.0)
Divider()
}
}
.frame(height: 100.0)
}
.padding(.all, 5)
Divider()
// add the daily temperature chart
Divider()
Like the day details list, this section iterates over the days in dailyForecast
to display the moon phase symbol from day.moon.phase.symbolName
. Ignore the comment for now; you’ll address the charts later.
To view your changes, build and run. Switch to the Detailed tab and choose a location:
Scroll to view the details. In the next section, you’ll showcase the hourly overview.