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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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))
Note: You can download the SF Symbols on your Mac from here.

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.

Locations list view

Tap a city. You’ll see an empty view with the city name and “Detailed forecast” label. You’ll implement the view right now!

Empty detailed forecast view with the location name only

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.

Note: Some properties, like 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:

Detailed 10-day forecast and moon phases for Rome

Scroll to view the details. In the next section, you’ll showcase the hourly overview.