A native Swift wrapper around Apple's WeatherKit REST API
Bring WeatherKit to Linux, servers, and every platform Apple doesn't support β with an API nearly identical to Apple's own WeatherKit.
- Requirements
- Prerequisites
- Installation
- Apple Developer Portal Setup
- Generating the JWT
- Usage
- Attribution
Minimum Swift version of 5.9.
| Platform | Minimum Version |
|---|---|
| iOS | 13+ |
| watchOS | 6+ |
| tvOS | 13+ |
| visionOS | 1+ |
| macOS | 11+ |
| Ubuntu | 18.04+ |
Before you can make a single request, you need the following from Apple. Each of these is created in the Apple Developer Portal and is required to build the JSON Web Token (JWT) that authenticates every WeatherKit REST call.
| What you need | Used for | Where it comes from |
|---|---|---|
| Apple Developer Program membership (paid) | Access to WeatherKit | developer.apple.com/programs |
| App ID with the WeatherKit capability enabled | Grants your identifiers access to the WeatherKit service | Identifiers β App IDs |
| Services ID (Identifier) | JWT sub (subject) claim |
Identifiers β Services IDs |
WeatherKit private key (.p8) + Key ID |
Signs the JWT (kid header) |
Keys |
| Team ID | JWT iss (issuer) claim |
Membership |
Follow the Apple Developer Portal Setup section below for step-by-step instructions on generating each of these.
- In Xcode, open your project and choose File βΈ Add Package Dependenciesβ¦
- In the search field, paste the package URL:
https://github.com/kushal211/AppleWeatherKit.git - Choose the Dependency Rule (e.g. Up to Next Major Version) and click Add Package.
- Select the AppleWeatherKit library product and add it to your app target.
import AppleWeatherKitwherever you need it.
Add the dependency to your Package.swift:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "YourApp",
dependencies: [
.package(url: "https://github.com/kushal211/AppleWeatherKit.git", from: "1.0.0"),
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "AppleWeatherKit", package: "AppleWeatherKit"),
]
),
]
)If you keep AppleWeatherKit inside your repository (for example under LocalPackages/AppleWeatherKit),
reference it by path instead of by URL.
In Xcode: choose File βΈ Add Package Dependenciesβ¦ βΈ Add Localβ¦, then select the
AppleWeatherKit folder and add the AppleWeatherKit product to your target.
In Package.swift:
dependencies: [
.package(path: "LocalPackages/AppleWeatherKit"),
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "AppleWeatherKit", package: "AppleWeatherKit"),
]
),
]If you prefer not to use SPM, you can add the sources directly:
- Clone or download this repository.
- Drag the
Sources/AppleWeatherKitfolder into your Xcode project. When prompted, tick Copy items if needed and Create groups, and add it to the relevant target(s). - Make sure the
Sources/AppleWeatherKit/Resourcesfolder is included so the bundled localization (Localizable.strings) ships with your app. import AppleWeatherKitand build.
Note: On Linux the package depends on async-http-client; SPM resolves this automatically. Manual installation is intended for Apple platforms.
The REST API requires a signed JWT to be sent with each request. Complete all four steps below.
- Go to Identifiers.
- Select (or create with the + button) the App ID you use for your app.
- In the list of capabilities, tick WeatherKit.
- Click Save (confirm the modification if prompted).
Enabling WeatherKit on the App ID is what actually grants your account access to the service. Without this step, requests will fail even with a correctly signed JWT.
- Go to Identifiers.
- On the top left, click the add button (+), select Services IDs, then click Continue.
- Register a Service ID (provide a description and a reverse-domain identifier, e.g.
com.yourcompany.weather). - Make note of the Identifier β this is your
SERVICE_IDENTIFIER(the JWTsubclaim). - Click Continue, review the registration information, and click Register.
- Go to Keys.
- On the top left, click the add button (+).
- Give your key a name and tick the WeatherKit box.
- Click Continue, review the registration information, and click Register.
- Make note of the Key ID β this is your
KEY_ID(the JWTkidheader). - Download the private key (
.p8file). You can only download it once β store it securely.
Your Team ID is your TEAM_ID (the JWT iss claim). Find it under
Membership details in the developer portal.
The WeatherKit REST API requires a JSON Web Token (JWT) to be sent with every request. Implementing the logic necessary to generate a JWT is beyond the scope of the AppleWeatherKit project at this time. For general information on JWT please visit https://jwt.io
That being said, the recommended package to handle this task is Vapor's jwt-kit. Here is how to set that up:
Implement a model conforming to JWTPayload:
import JWTKit
struct Payload: JWTPayload, Equatable {
enum CodingKeys: String, CodingKey {
case expiration = "exp"
case issued = "iat"
case issuer = "iss"
case subject = "sub"
}
let expiration: ExpirationClaim
let issued: IssuedAtClaim
let issuer: IssuerClaim
let subject: SubjectClaim
func verify(using key: some JWTAlgorithm) throws {}
}Generate the JWT:
struct JWTProvider {
static func generate() async throws -> String {
let keys = JWTKeyCollection()
try await keys.add(ecdsa: ES256PrivateKey(pem: PRIVATE_KEY_FROM_DEV_PORTAL))
let payload = Payload(
expiration: .init(value: .distantFuture),
issued: .init(value: .now),
issuer: TEAM_ID,
subject: SERVICE_IDENTIFIER
)
return try await keys.sign(payload, kid: KEY_ID)
}
}Note the variables:
PRIVATE_KEY_FROM_DEV_PORTAL: The contents of the private key file including
-----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----
TEAM_ID: Found in Membership Details on the developer portal
SERVICE_IDENTIFIER: The reverse-domain name noted earlier
KEY_ID: The ID of the service key
import AppleWeatherKit in any file where you use the service.
The service must be initialized with a JWT generating closure and optionally a language.
import AppleWeatherKit
let weatherService = WeatherService(
configuration: .init(jwt: { try await JWTProvider.generate() })
)let weather = try await weatherService
.weather(
for: Location(
latitude: 37.541290,
longitude: -77.511429),
countryCode: "US"
)let (dailyForecast, hourlyForecast, alerts) = try await weatherService
.weather(
for: Location(
latitude: 37.541290,
longitude: -77.511429),
including: .daily, .hourly, .alerts(countryCode: "US")
)Note that minute forecasts and alerts are not always available in all regions. Use the .availability query check their availability.
let availability = try await weatherService
.weather(
for: Location(
latitude: 37.541290,
longitude: -77.511429),
including: .availability
)Historical weather statistics are derived from weather data recorded over the past decades. Statistics are available at daily, hourly, and monthly intervals.
Daily Statistics (30 days ago to 10 days from now by default):
let (dailyPrecipitation, dailyTemperature) = try await weatherService
.dailyStatistics(
for: Location(latitude: 37.541290, longitude: -77.511429),
including: .precipitation, .temperature
)Daily Statistics (specific day range, 1-366):
let (dailyPrecipitation, dailyTemperature) = try await weatherService
.dailyStatistics(
for: Location(latitude: 37.541290, longitude: -77.511429),
startDay: 1,
endDay: 10,
including: .precipitation, .temperature
)Hourly Statistics (24 hours of current day by default):
let hourlyTemperature = try await weatherService
.hourlyStatistics(
for: Location(latitude: 37.541290, longitude: -77.511429),
including: .temperature
)Hourly Statistics (specific hour range, 1-8784):
let hourlyTemperature = try await weatherService
.hourlyStatistics(
for: Location(latitude: 37.541290, longitude: -77.511429),
startHour: 1,
endHour: 24,
including: .temperature
)Monthly Statistics (all 12 months by default):
let (monthlyPrecipitation, monthlyTemperature) = try await weatherService
.monthlyStatistics(
for: Location(latitude: 37.541290, longitude: -77.511429),
including: .precipitation, .temperature
)Monthly Statistics (specific month range, 1-12):
let (monthlyPrecipitation, monthlyTemperature) = try await weatherService
.monthlyStatistics(
for: Location(latitude: 37.541290, longitude: -77.511429),
startMonth: 1,
endMonth: 6,
including: .precipitation, .temperature
)Weather summaries provide aggregated actual weather data (not statistics) for past dates.
Daily Summary (past 30 days by default):
let (dailyPrecipitation, dailyTemperature) = try await weatherService
.dailySummary(
for: Location(latitude: 37.541290, longitude: -77.511429),
including: .precipitation, .temperature
)Daily Summary (specific day range, 1-366):
let (dailyPrecipitation, dailyTemperature) = try await weatherService
.dailySummary(
for: Location(latitude: 37.541290, longitude: -77.511429),
startDay: 1,
endDay: 10,
including: .precipitation, .temperature
)Daily Summary (specific date interval):
let interval = DateInterval(start: startDate, end: endDate)
let (dailyPrecipitation, dailyTemperature) = try await weatherService
.dailySummary(
for: Location(latitude: 37.541290, longitude: -77.511429),
forDaysIn: interval,
including: .precipitation, .temperature
)When the library is used on an Apple platform, the countryCode and timezone parameters are not required. Internally, the library will use CoreLocation to reverse geocode the location to determine the country code. If the country cannot be determined, an error will be thrown.
A complete SwiftUI example app demonstrating every public API method is included under
Example/AppleWeatherKitExample.
To run it:
-
Open
Example/AppleWeatherKitExample/AppleWeatherKitExample.xcodeprojin Xcode. -
Open
WeatherKitCredentials.swiftβ all credentials live in this one file β and replace each placeholder with your own values from the Apple Developer Portal Setup:enum WeatherKitCredentials { static let teamID = "YOUR_TEAM_ID" // JWT `iss` β Membership details static let serviceIdentifier = "YOUR_SERVICE_IDENTIFIER" // JWT `sub` β Identifiers β Services IDs static let keyID = "YOUR_KEY_ID" // JWT `kid` β Keys (WeatherKit key) static let privateKey = """ -----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY_CONTENTS_HERE -----END PRIVATE KEY----- """ }
ExampleApp.swiftreads these values in itsJWTProviderto sign the token β you don't need to edit it. -
Select an iOS Simulator (or device) and run.
β οΈ Never commit real credentials.WeatherKitCredentials.swiftships with placeholders only. The private key is a secret β treat the.p8contents like a password and keep real values out of source control (e.g. via a git-ignored file, environment variables, or your build configuration).
Please be advised of Apple's attribution guidelines when using this package.
Attribution information can be accessed with:
let attribution = weatherService.attributionNote that this property returns a static WeatherAttribution instance using information from WeatherKit and is not guaranteed to be accurate or complete.