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

Many iOS apps use weather data as a supplementary feature in news apps or as crucial information that the app’s functionality hinges on, such as in planning or travel.

In 2020, Apple bought the Dark Sky weather app to enhance its macOS and iOS weather apps. Apple introduced WeatherKit at WWDC22, a framework for gathering weather data without relying on APIs or third-party SDKs.

If you choose to use a third-party API, it’s important to consider the extra factors involved, such as comprehending and creating a model for the response structure. If there isn’t a particular reason to get the information from another source, WeatherKit is the recommended choice.

In this tutorial, you’ll:

  • Discover WeatherKit and the information it offers.
  • Retrieve and show the weather forecast for your current location.
  • Use Swift Charts to plot detailed weather predictions for various locations.

You should already know Swift, iOS and Xcode basics for this tutorial.

Note: Use the latest version of Xcode 14 and a device or simulator with iOS 16.
Also, have an Apple Developer account to set up an App ID with the WeatherKit App Service.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial. Open the project and build and run.

App with an empty page

KodecoWeather is a weather app with two tabs:

  • Current: Which will show the current forecast for your location.
  • Detailed: Will offer a detailed forecast for a list of locations, including hourly and daily weather predictions.

Setting Up Your Project

To use WeatherKit, follow these initial steps to enable it in your project. You’ll first need to register a new App Identifier with a specific Bundle ID for activation.

Registering App Identifiers

Visit the Apple developer portal and sign in with your Apple ID. Select Identifiers under the Certificates, IDs & Profiles category. Click the “+” icon near Identifiers. For the next two steps, click Continue, maintaining the default options for App ID and App.

On the Register an App ID page, input an Explicit Bundle ID, such as com.[yourName].KodecoWeather, then provide a brief description.

Activating WeatherKit Capability

WeatherKit, like ShazamKit or iCloud, is an app service and feature that requires activation. On the Register an App ID page, select the App Services tab, then check the box next to WeatherKit. Click Continue to complete registration.

Displaying the WeatherKit app service

Note: After enabling WeatherKit, allow 30 minutes for activation. Requests before this timeframe won’t process.

In Xcode, open your starter project and access the Project Editor. Within Signing & Capabilities, ensure Automatically manage signing is checked, then enter the Bundle ID you specified earlier into Bundle identifier. Build and run.

App showcasing an empty screen

In the upcoming section, you’ll begin working with WeatherKit.

Using WeatherService

Open WeatherData.swift, noticing the four methods in the WeatherData class. Notice the following:

func currentWeather(for location: CLLocation) async -> CurrentWeather? {
  let currentWeather = await Task.detached(priority: .userInitiated) {
    let forecast = try? await self.service.weather(
      for: location,
      including: .current)
    return forecast
  }.value
  return currentWeather
}

This code takes one parameter of type CLLocation and returns a CurrentWeather type struct, which contains the current weather data for that location. It calls the WeatherService method of WeatherKit named weather(for:including:), which takes two parameters:

  • A CLLocation, for which the weather forecast is retrieved.
  • A WeatherQuery, which specifies the forecast time. Here, .current is passed to get the current forecast.
Note: Learn more about async/await usage in SwiftUI from Async/Await in SwiftUI by Audrey Tam.

The following two methods, dailyForecast(for:) and hourlyForecast(for:), are like the first method. But different forecasts are queried from the WeatherService using .daily and .hourly, respectively.

WeatherKit provides WeatherService.weather(for:including:) as the primary method for data requests. You can use many overloads to request up to five weather queries for a location in one request. For instance, you could write:

let (current, daily, hourly) = try await service.weather(for: location, including: .current, .daily, .hourly)

This query requests the current, daily and hourly forecasts at the same time. For simplicity, this tutorial uses one weather query per call.

The following section discusses the display of the current forecast for your location.

Displaying the Current Weather Forecast

Now, you’ll implement the app’s first section, which will:

  • Obtain the user’s location.
  • Query the WeatherService for that location.
  • Display the desired weather measurements from the response.

First, open CurrentWeatherView.swift in the Views folder. Notice the first three variable definitions:

  • locationManager: An instance of the LocationManager helper class. This requests your location from CoreLocation.
  • weatherServiceHelper: Initialized with the singleton of WeatherData. This is the helper class observed in the previous section.
  • currentWeather: A state variable where the CurrentWeather data from WeatherKit is stored.

Time to start coding. First you need to define a method that LocationManager should call after obtaining a location. Add the following below the body view:

func locationUpdated(location: CLLocation?, error: Error?) {
  if let currentLocation: CLLocation = location, error == nil {
    Task.detached {
      isLoading = false
      currentWeather = await weatherServiceHelper.currentWeather(for: currentLocation)
      stateText = ""
    }
  } else {
    stateText = "Cannot get your location. \n \(error?.localizedDescription ?? "")"
    isLoading = false
  }
}

This code first checks that a location is returned without error. It then:

  • Sets isLoading to false to hide the ProgressView.
  • Calls the currentWeather(for:) method of WeatherServiceHelper, passing the location. Once execution completes, the response of type CurrentWeather is assigned to the state variable.
  • Then, stateText is set to remove any previously set “loading” or error text.
  • If a valid location is not retrieved, the error message is set in stateText.

To start the LocationManager, add the following lines inside the View’s onAppear closure:

isLoading = true
self.locationManager.updateLocation(handler: locationUpdated)

Here, you set isLoading to true, which causes the ProgressView to be displayed. updateLocation(handler:) is then called, passing the handler method that you added earlier.

Lastly, the retrieved forecast should be displayed to the user. Immediately below these lines in the VStack block:

if isLoading {
  ProgressView()
}

Add the following:

if let current = currentWeather {
  Image(systemName: current.symbolName)
    .font(.system(size: 75.0, weight: .bold))

  Text(current.condition.description)
    .font(Font.system(.largeTitle))

  let tUnit = current.temperature.unit.symbol
  Text("\(current.temperature.value.formatted(.number.precision(.fractionLength(1))))\(tUnit)")
    .font(Font.system(.title))

  Spacer()

  VStack(alignment: .leading) {
    Text("Feels like: \(current.apparentTemperature.value.formatted(.number.precision(.fractionLength(1)))) \(tUnit)")
      .font(Font.system(.title2))
    Text("Humidity: \((current.humidity * 100).formatted(.number.precision(.fractionLength(1))))%")
      .font(Font.system(.title2))
    Text("Wind Speed: \(Int(current.wind.speed.value)), \(current.wind.compassDirection.description)")
      .font(Font.system(.title2))
    Text("UV Index: \(current.uvIndex.value)")
      .font(Font.system(.title2))
  }
  Spacer()
  Divider()
} else {
  Text(stateText)
}

Here, you present many of the forecast parameters returned in currentWeather. Build and run to see the results.

Current weather forecast for the user's current location

Note: If it’s been less than 30 minutes since you registered the App ID, WeatherKit requests won’t work. You’ll see the following authentication error in the console:

Grab a coffee or snack!

[AuthService] Failed to generate jwt token for com.apple.weatherkit.authservice with error: Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)"
[AuthService] Failed to generate jwt token for com.apple.weatherkit.authservice with error: Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)"

In the next section, you’ll explore the forecast data WeatherKit returns.