A Swift wrapper for the National Weather Service (NWS) API that provides easy access to weather data including forecasts, alerts, and observations.
- Swift-idiomatic API design with async/await support
- Comprehensive coverage of NWS API endpoints
- Type-safe models for all API responses
- Robust error handling
- NO dependencies
- Detailed documentation and examples
- iOS 15.0+ / macOS 12.0+ / tvOS 15.0+ / watchOS 8.0+
- Swift 5.5+
- Xcode 13.0+
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/iOSDeveloperGuy/SwiftNWS.git", from: "1.0.0")
]Or add it directly in Xcode using File > Swift Packages > Add Package Dependency.
import SwiftNWS
// Initialize with default configuration
let nwsClient = NWSClient()
// Or with custom configuration
let config = NWSConfiguration(
baseURL: URL(string: "https://api.weather.gov")!,
userAgent: "MyWeatherApp/1.0 (myapp.com, contact@myapp.com)",
defaultFormat: .geoJSON,
timeoutInterval: 30.0
)
let nwsClient = NWSClient(configuration: config)// Get forecast for a specific location by coordinates
do {
let forecast = try await nwsClient.forecasts.getForecastForPoint(
latitude: 39.7456,
longitude: -97.0892
)
// Access forecast data
for period in forecast.properties.periods {
print("\(period.name): \(period.detailedForecast)")
}
} catch {
print("Error fetching forecast: \(error)")
}
// Get hourly forecast
do {
let hourlyForecast = try await nwsClient.forecasts.getHourlyForecastForPoint(
latitude: 39.7456,
longitude: -97.0892
)
// Access hourly forecast data
for period in hourlyForecast.properties.periods {
print("\(period.startTime) - \(period.temperature)°\(period.temperatureUnit)")
}
} catch {
print("Error fetching hourly forecast: \(error)")
}// Get all active alerts
do {
let activeAlerts = try await nwsClient.alerts.getActiveAlerts()
for alert in activeAlerts.features {
print("Alert: \(alert.event) - \(alert.headline ?? "No headline")")
print("Severity: \(alert.severity.rawValue)")
print("Areas: \(alert.areaDesc)")
}
} catch {
print("Error fetching alerts: \(error)")
}
// Get alerts for a specific area
do {
let areaAlerts = try await nwsClient.alerts.getActiveAlertsForArea("CA")
print("Found \(areaAlerts.features.count) active alerts in California")
} catch {
print("Error fetching area alerts: \(error)")
}// Get latest observations for a station
do {
let observations = try await nwsClient.observations.getLatestObservations(stationId: "KNYC")
if let temp = observations.properties.temperature?.value {
print("Current temperature: \(temp)°C")
}
if let description = observations.properties.textDescription {
print("Conditions: \(description)")
}
} catch {
print("Error fetching observations: \(error)")
}
// Get observations for a time period
do {
let startDate = Date().addingTimeInterval(-86400) // 24 hours ago
let endDate = Date()
let observations = try await nwsClient.observations.getObservations(
stationId: "KNYC",
start: startDate,
end: endDate
)
print("Retrieved \(observations.features.count) observations")
} catch {
print("Error fetching observations: \(error)")
}// Get stations near a point
do {
let stations = try await nwsClient.stations.getStationsForPoint(
latitude: 39.7456,
longitude: -97.0892
)
for station in stations.features {
print("Station: \(station.properties.name) (\(station.properties.stationIdentifier))")
}
} catch {
print("Error fetching stations: \(error)")
}
// Get specific station
do {
let station = try await nwsClient.stations.getStation(stationId: "KNYC")
print("Station: \(station.properties.name)")
print("Time Zone: \(station.properties.timeZone ?? "Unknown")")
} catch {
print("Error fetching station: \(error)")
}// Get all forecast zones
do {
let zones = try await nwsClient.zones.getZones(type: .forecast)
print("Retrieved \(zones.features.count) forecast zones")
} catch {
print("Error fetching zones: \(error)")
}
// Get specific zone
do {
let zone = try await nwsClient.zones.getZone(type: .forecast, zoneId: "NYZ072")
print("Zone: \(zone.properties.name)")
} catch {
print("Error fetching zone: \(error)")
}
// Get forecast for a zone
do {
let forecast = try await nwsClient.zones.getForecastForZone(type: .forecast, zoneId: "NYZ072")
for period in forecast.properties.periods {
print("\(period.name): \(period.detailedForecast)")
}
} catch {
print("Error fetching zone forecast: \(error)")
}// Get all offices
do {
let offices = try await nwsClient.offices.getAllOffices()
print("Retrieved \(offices.features.count) offices")
} catch {
print("Error fetching offices: \(error)")
}
// Get specific office
do {
let office = try await nwsClient.offices.getOffice(officeId: "OKX")
print("Office: \(office.properties.name)")
} catch {
print("Error fetching office: \(error)")
}
// Get headlines for an office
do {
let headlines = try await nwsClient.offices.getHeadlinesForOffice(officeId: "OKX")
for headline in headlines.features {
print("Headline: \(headline.properties.title)")
}
} catch {
print("Error fetching headlines: \(error)")
}// Get the latest SPC Day 1 convective outlook narrative
do {
let outlook = try await nwsClient.products.getLatestSPCOutlook(.day1)
print("Issued: \(outlook.issuanceTime)")
print(outlook.productText ?? "No product text")
} catch {
print("Error fetching SPC outlook: \(error)")
}
// You can also query the underlying text products API directly
do {
let latestDay2 = try await nwsClient.products.getLatestProduct(
typeId: "SWO",
locationId: "DY2"
)
print(latestDay2.productName)
} catch {
print("Error fetching latest SWO DY2 product: \(error)")
}// Fetch the latest Day 1 categorical polygons for map rendering
do {
let outlook = try await nwsClient.spcOutlooks.getOutlookGeometry(for: .day1Categorical)
for feature in outlook.features {
for polygon in feature.geometry.coordinatePolygons {
for ring in polygon {
print("Ring has \(ring.count) coordinates")
}
}
}
} catch {
print("Error fetching SPC outlook geometry: \(error)")
}The wrapper uses Swift's built-in error handling mechanisms. All service methods are marked with throws and will throw appropriate NWSError instances when errors occur.
do {
let forecast = try await nwsClient.forecasts.getForecastForPoint(
latitude: 39.7456,
longitude: -97.0892
)
// Process forecast
} catch let error as NWSError {
switch error {
case .rateLimitExceeded:
print("Rate limit exceeded. Please try again later.")
case .networkError(let underlyingError):
print("Network error: \(underlyingError.localizedDescription)")
case .serverError(let statusCode, let message):
print("Server error (\(statusCode)): \(message ?? "No message")")
case .notFound:
print("Resource not found")
default:
print("Error: \(error.localizedDescription)")
}
} catch {
print("Unexpected error: \(error)")
}// Configure client to use a different default format
let config = NWSConfiguration(defaultFormat: .jsonLD)
let nwsClient = NWSClient(configuration: config)The NWS API requires a User-Agent header to identify your application. It's recommended to include contact information.
nwsClient.setUserAgent("MyWeatherApp/1.0 (myapp.com, contact@myapp.com)")- Set a descriptive User-Agent: Include your app name, version, and contact information.
- Handle rate limits: The NWS API has rate limits. If you receive a
.rateLimitExceedederror, wait before retrying. - Cache responses: Weather data doesn't change frequently. Consider caching responses to reduce API calls.
- Handle errors gracefully: Provide meaningful feedback to users when API calls fail.
- Use appropriate endpoints: Use the most specific endpoint for your needs to minimize data transfer.
This project is available under the MIT license. See the LICENSE file for more info.
This wrapper is not affiliated with or endorsed by the National Weather Service or NOAA.
For questions, issues, or feature requests, please open an issue on the GitHub repository.