---
name: weatherkit
description: "WeatherKit on iOS: current, hourly, daily and minute forecasts, alerts, historical statistics, dataset selection, caching. Use when integrating weather data, showing forecasts, handling severe alerts or displaying the required Apple Weather attribution and legal link."
metadata:
  source: multi-agent-pipeline
---

# WeatherKit

WeatherKit gives an app the same forecast data the system Weather app uses,
through one async service. The rules that matter most are legal and economic:
attribution is required wherever the data shows, and every dataset you request
counts against the app's quota. Code targets Swift 6.3 and iOS 26.

Dashboard, charts, historical statistics, condition mapping, caching and
location plumbing are in
[references/weatherkit-patterns.md](references/weatherkit-patterns.md).

## Setup

### Project configuration

1. In Xcode, add the **WeatherKit** capability. This writes the entitlement.
2. In the developer portal, enable WeatherKit for the same App ID (both the
   capability and the service checkbox are needed).
3. The account must be a paid, currently active developer program membership.
4. If the app sends the device's own position, add
   `NSLocationWhenInUseUsageDescription` and get location permission first.

### Imports and the service

```swift
import WeatherKit
import CoreLocation

let forecaster = WeatherService.shared   // or WeatherService()
```

`WeatherService` is `Sendable`, so one instance can be used from any task or
actor.

## Fetching current weather

The plain call returns every dataset in a `Weather` value:

```swift
let site = CLLocation(latitude: 38.42, longitude: 27.14)
let report = try await forecaster.weather(for: site)
let now = report.currentWeather
```

`CurrentWeather` fields you will use most:

| Property | Type | Note |
| --- | --- | --- |
| `temperature` | `Measurement<UnitTemperature>` | Format with `.formatted()` |
| `condition` | `WeatherCondition` | Enum with a readable `description` |
| `symbolName` | `String` | An SF Symbol name |
| `humidity` | `Double` | Fraction from 0 to 1 |
| `wind` | `Wind` | `speed`, `direction`, `gust` |
| `uvIndex` | `UVIndex` | `value` and `category` |

```swift
headline = "\(now.temperature.formatted()), \(now.condition.description)"
```

## Forecasts

### Hourly

At the time of writing, Apple's documentation says the default hourly forecast
covers 25 consecutive hours starting with the current hour; read the count from
the result rather than hard-coding it. `hourlyForecast` is a `Forecast<HourWeather>`:

```swift
let timeline = report.hourlyForecast.map { slot in
    (slot.date.formatted(.dateTime.hour()), slot.temperature.formatted(), slot.condition.description)
}
```

### Daily

At the time of writing, Apple's documentation says the default daily forecast
covers 10 consecutive days starting today; again, read the count from the
result.
`dailyForecast` is a `Forecast<DayWeather>`:

```swift
for day in report.dailyForecast {
    let range = "\(day.lowTemperature.formatted()) to \(day.highTemperature.formatted())"
    let rain = day.precipitationChance.formatted(.percent)
    rows.append("\(day.condition.description), \(range), rain \(rain)")
}
```

### A custom range

```swift
func nextWeek(at site: CLLocation) async throws -> Forecast<DayWeather>? {
    let weekStart = Date.now
    guard let weekEnd = Calendar.current.date(byAdding: DateComponents(day: 7), to: weekStart) else { return nil }
    let service = WeatherService.shared
    return try await service.weather(
        for: site,
        including: .daily(startDate: weekStart, endDate: weekEnd)
    )
}
```

`.hourly(startDate:endDate:)` works the same way for hours.

## Weather alerts

`weatherAlerts` is `[WeatherAlert]?`; it is `nil` where alerts are not
provided.

```swift
if let alerts = report.weatherAlerts {
    for alert in alerts {
        // alert.summary: String, alert.severity: WeatherSeverity,
        // alert.region: String?, alert.detailsURL: URL, alert.source: String
        banners.append(alert.summary)
    }
}
```

`detailsURL` is not optional; link to it so users can read the full notice
from the issuing agency.

## Selective queries

Ask only for what the screen shows. Each `WeatherQuery` maps to one dataset,
and each dataset is a request against the quota and more bytes over the wire.

```swift
// One dataset: the return type is the dataset itself.
let nowOnly: CurrentWeather = try await forecaster.weather(for: site, including: .current)

// Several datasets: the results come back as a tuple, in the order requested.
let (now, hours, days) = try await forecaster.weather(for: site, including: .current, .hourly, .daily)

// Next-hour precipitation, minute by minute. Only some regions have it.
if let nextHour = try await forecaster.weather(for: site, including: .minute) {
    rainStartsSoon = nextHour.contains { $0.precipitationChance > 0.5 }
}
```

| Query | Result type | Notes |
| --- | --- | --- |
| `.current` | `CurrentWeather` | |
| `.hourly` | `Forecast<HourWeather>` | Default span per Apple's docs: 25 hours from now |
| `.daily` | `Forecast<DayWeather>` | Default span per Apple's docs: 10 days from today |
| `.minute` | `Forecast<MinuteWeather>?` | `nil` where unsupported |
| `.alerts` | `[WeatherAlert]?` | `nil` where unsupported |
| `.availability` | `WeatherAvailability` | Which region-limited datasets exist here |

## Attribution

Showing Apple Weather attribution is a legal requirement of using WeatherKit,
not a design choice. Fetch it from the service:

```swift
let credit = try await forecaster.attribution
```

Pick the mark that matches the color scheme and link to the legal page:

```swift
struct WeatherCredit: View {
    let credit: WeatherAttribution
    @Environment(\.colorScheme) private var scheme

    var body: some View {
        VStack(spacing: 6) {
            AsyncImage(url: scheme == .dark ? credit.combinedMarkDarkURL : credit.combinedMarkLightURL) { mark in
                mark.resizable().scaledToFit()
            } placeholder: {
                Text(credit.serviceName).font(.caption2)
            }
            .frame(height: 14)

            Link("Other data sources", destination: credit.legalPageURL)
                .font(.caption2)
        }
    }
}
```

`WeatherAttribution` provides:

| Property | Use |
| --- | --- |
| `combinedMarkLightURL` | Mark for light backgrounds |
| `combinedMarkDarkURL` | Mark for dark backgrounds |
| `squareMarkURL` | Compact square mark |
| `legalPageURL` | Page listing data sources; link to it |
| `legalAttributionText` | Plain-text alternative when showing a web page is not practical |
| `serviceName` | The provider name, "Apple Weather" |

## Availability

Minute forecasts and alerts are not offered in every country. Ask before
building UI around them:

```swift
let coverage = try await forecaster.weather(for: site, including: .availability)
let offersAlerts = coverage.alertAvailability == .available
let offersNextHour = coverage.minuteAvailability == .available
```

Each is a `WeatherAvailability.AvailabilityKind`: `.available`,
`.temporarilyUnavailable`, `.unsupported` or `.unknown`.

## Common mistakes

1. **No attribution.** Leaving it out breaks the WeatherKit terms and is a
   reason for App Review to reject the app.
2. **Fetching everything to show one number.** `weather(for:)` returns every
   dataset and each one counts against the quota. Use
   `weather(for:including: .current)` when only current conditions appear.
3. **Force-unwrapping the minute forecast.** It is `nil` in unsupported
   regions and the app crashes there. Use `if let`.
4. **Missing capability.** Without the capability and the portal switch,
   `WeatherService` calls throw at runtime.
5. **Refetching on every appearance.** Weather does not change second to
   second. Cache results with a staleness window, for example an actor that
   keeps entries for 600 seconds (see the reference file).

## Review checklist

- [ ] WeatherKit capability added in Xcode and enabled in the portal
- [ ] Active developer program membership
- [ ] Attribution visible wherever weather data appears
- [ ] Attribution mark follows light and dark mode
- [ ] Legal page linked, or `legalAttributionText` shown
- [ ] Only the datasets the screen needs are requested
- [ ] Minute forecast treated as optional
- [ ] Alerts nil-checked
- [ ] Responses cached for 5 to 15 minutes
- [ ] `WeatherAvailability` checked before region-limited datasets
- [ ] Location permission granted before a device `CLLocation` is passed in
- [ ] Measurements displayed with `formatted()` so units follow the locale

## References

- [references/weatherkit-patterns.md](references/weatherkit-patterns.md)
- [WeatherKit](https://developer.apple.com/documentation/weatherkit)
- [WeatherService](https://developer.apple.com/documentation/weatherkit/weatherservice)
- [WeatherAttribution](https://developer.apple.com/documentation/weatherkit/weatherattribution)
- [WeatherQuery](https://developer.apple.com/documentation/weatherkit/weatherquery)
- [CurrentWeather](https://developer.apple.com/documentation/weatherkit/currentweather)
- [Forecast](https://developer.apple.com/documentation/weatherkit/forecast)
- [HourWeather](https://developer.apple.com/documentation/weatherkit/hourweather)
- [DayWeather](https://developer.apple.com/documentation/weatherkit/dayweather)
- [WeatherAlert](https://developer.apple.com/documentation/weatherkit/weatheralert)
- [WeatherAvailability](https://developer.apple.com/documentation/weatherkit/weatheravailability)
- [Fetching weather forecasts with WeatherKit](https://developer.apple.com/documentation/weatherkit/fetching_weather_forecasts_with_weatherkit)
