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 3 of 4 of this article. Click here to view the first page.

Presenting the Hourly Overview

For this, you’ll implement a horizontal list to display an overview of the weather condition every hour for the next 25 hours, starting from the current hour. As with the day’s list, you’ll create the hour details view first.

Open HourDetailsCell.swift and add the following variable definition at the start of the view struct:

var hourWeather: HourWeather?

This variable stores the HourWeather, representing the forecast for an individual hour. The list iterator provides it. In the view body scope, replace the existing code with:

if let hour = hourWeather {
  VStack {
    Text(hour.date.formatted(.dateTime.hour()))
      .font(.system(size: 13.0))
    Divider()

    Image(systemName: hour.symbolName)
      .font(.system(size: 22.0, weight: .bold))
      .padding(.bottom, 3.0)

    Text("\((hour.precipitationChance * 100)
         .formatted(.number.precision(.fractionLength(1))))%")
      .foregroundColor(.blue)
      .font(.system(size: 15.0))
  }
}

This view shows the weather condition icon and the precipitation chance. The precipitationChance property returns as a Double. To display the percentage, you multiply it by 100 and show only one decimal place.

Now, open DetailedWeatherView.swift and insert the following code below the scope of if let daily = dailyForecast { ... }:

if let hourly = hourlyForecast {
  Text("Hourly Forecast").font(Font.system(.title))
  Text("Next 25 hours").font(Font.system(.caption))
  
  ScrollView(.horizontal) {
    HStack(spacing: 15) {
      ForEach(hourly, id: \.date) { hour in
        HourDetailsCell(hourWeather: hour)
        Divider()
      }
    }
    .frame(height: 100.0)
  }
  .padding(.all, 5)

  Divider()

  // Insert the hourly temperature chart here

  Divider()
}

Here, you iterate over the hours in hourlyForecast and created an HourDetailsCell for each hour.

Now, build and run. Go to the Detailed tab and choose a location.

Detailed 10-day forecast, moon phases and the 25-hour forecast for Madrid

Note: One of the big advantages of using SF Symbols is that you don’t need to worry about whether iOS dark mode is on or off. The symbols adapt to the device settings. Try turning on dark mode on your iPhone or, if you’re using the Simulator simply press Shift-Command-A:Detailed forecast view in dark mode

Note: One of the big advantages of using SF Symbols is that you don’t need to worry about whether iOS dark mode is on or off. The symbols adapt to the device settings. Try turning on dark mode on your iPhone or, if you’re using the Simulator simply press Shift-Command-A:Detailed forecast view in dark mode

Next, you’ll introduce the daily temperature chart.

Adding a Daily Temperature Chart

Charts play a crucial role in visualizing weather data. They simplify intricate data, allowing for quick comprehension. Fortunately, you don’t need a third-party framework for charts! At WWDC 2022, Apple unveiled a SwiftUI framework for charts.

In this and the following section, you’ll use this framework to construct two charts:

  • A chart displaying temperature ranges: high, average and low for a 10-day span.
  • A chart illustrating the temperature over 25 hours.

For both charts, the X axis represents time — either the day or hour — while the Y axis denotes the temperature.

To set the Y-axis scale in both charts, find the minimum and maximum temperatures for the respective periods. Two helper methods assist with this. Open WeatherData.swift and examine findDailyTempMinMax(_:) in WeatherDataHelper:

public static func findDailyTempMinMax(_ daily: Forecast) -> (min: Double, max: Double) {
  let minElement = daily.min { valA, valB in
    valA.lowTemperature.value < valB.lowTemperature.value
  }
  let min = minElement?.lowTemperature.value ?? 0
  let maxElement = daily.max { valA, valB in
    valA.highTemperature.value < valB.highTemperature.value
  }
  let max = maxElement?.highTemperature.value ?? 200
  return (min, max)
}

This straightforward method accepts Forecast and returns a tuple containing the minimum and maximum temperatures.

Because Forecast is a RandomAccessCollection, it adheres to the Sequence protocol. The method employs Sequence.min(by:) to identify the minimum and Sequence.max(by:) to pinpoint the maximum.

The next helper method, findHourlyTempMinMax(_:), performs the same task but for the hourly forecast Forecast.

Now, create the daily chart. Open DetailedWeatherView.swift and search for this comment:

// Add the daily temperature chart

Replace the comment with the following:

let range = WeatherDataHelper.findDailyTempMinMax(daily)

Chart(daily, id: \.date) { day in
  LineMark( //1
    x: .value("Day", day.date),
    y: .value("Temperature", (day.lowTemperature.value
                              + day.highTemperature.value) / 2)
  )
  .foregroundStyle(.orange)
  .symbol(.circle)
  .interpolationMethod(.catmullRom)
  AreaMark( //2
    x: .value("Day", day.date),
    yStart: .value("Low", day.lowTemperature.value),
    yEnd: .value("High", day.highTemperature.value)
  )
  .foregroundStyle(
    Gradient(
      colors: [
        .orange.opacity(0.4),
        .blue.opacity(0.4)
      ]
    )
  )
}
.chartForegroundStyleScale([ //3
  "avg": .orange,
  "low": .blue,
  "high": .orange
]) 
.chartYScale(domain: range.min - 5...range.max + 5) //4
.chartYAxisLabel(daily[0].lowTemperature.unit.symbol) //5
.chartXAxis { //6
  AxisMarks(values: .automatic(minimumStride: 10, desiredCount: 10)) { _ in
    AxisGridLine()
    AxisTick()
    AxisValueLabel(format: .dateTime.day())
  }
}
.frame(
  height: 350.0
)

Look at the code. First, you call findDailyTempMinMax(_:) to get the temperatures range as explained above and store the result in the range variable. Then, you construct the chart passing the daily forecast data as follows:

  1. Draw the average temperature line by defining a LineMark with the date as the X axis and the temperature as the Y axis. Notice how you calculate the average (day.lowTemperature.value + day.highTemperature.value) / 2. The line is going to be orange for contrast.
  2. Draw an area highlighting the high and low temperatures by defining an AreaMark that starts from the lowTemperature value up to the highTemperature value. You set the area foreground to a semi-transparent gradient ranging from blue to orange.
  3. Add a legend with the colors representing low, average and high temperatures.
  4. Set the Y-axis scale, which determines the lowest and highest values displayed in the chart. However, you don’t want the highest temperature of the period to be at the very top of the chart or the lowest one at the very bottom. You want to center the line and area for better readability. To do that, you set the scale to range from five degrees below the lowest temperature and five degrees above the highest.
  5. Set the Y-axis label to show the temperature unit symbol.
  6. Set the date format in the X-axis to show only the day part of the date.

To see the chart, build and run. Switch to the Detailed tab and select one of the locations.

Daily temperature chart in the detailed view

In the next section, you’ll add the hourly temperature chart.

Adding an Hourly Temperature Chart

Begin by opening DetailedWeatherView.swift and searching for the comment:

// Insert the hourly temperature chart here

Next, substitute the comment with:

let range = WeatherDataHelper.findHourlyTempMinMax(hourly)

Chart(hourly, id: \.date) { hour in
  LineMark(
    x: .value("Day", hour.date),
    y: .value("Temperature", hour.temperature.value)
  )
  .foregroundStyle(.orange)
  .symbol(.circle)
  .interpolationMethod(.catmullRom)
}
.chartYScale(domain: range.min - 5...range.max + 5)
.chartYAxisLabel(hourly[0].temperature.unit.symbol)
.chartXAxis {
  AxisMarks(values: .automatic(minimumStride: 10)) { _ in
    AxisGridLine()
    AxisTick()
    AxisValueLabel(
      format: .dateTime.hour().minute()
    )
  }
}
.frame(
  height: 350.0
)

This code is very similar to the daily chart code. You first get the minimum and maximum temperatures, then construct a chart with a line indicating the temperature for each hour.

Note: To know more about using the Charts framework, check out Swift Charts Tutorial: Getting Started by Vidhur Voora.

Build and run. Navigate to the Detailed tab and choose a location.

25-hour temperature chart

You’ll prepare your app for distribution in the upcoming section.