Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Overflow reference for the weatherkit skill. Contains advanced patterns that exceed the main skill file's scope.
@Observableimport WeatherKit
import CoreLocation
@Observable
@MainActor
final class WeatherManager {
private let service = WeatherService.shared
var current: CurrentWeather?
var hourlyForecast: Forecast<HourWeather>?
var dailyForecast: Forecast<DayWeather>?
var alerts: [WeatherAlert]?
var attribution: WeatherAttribution?
var isLoading = false
var error: Error?
func fetchWeather(for location: CLLocation) async {
isLoading = true
error = nil
do {
let (current, hourly, daily, alerts) = try await service.weather(
for: location,
including: .current, .hourly, .daily, .alerts
)
self.current = current
self.hourlyForecast = hourly
self.dailyForecast = daily
self.alerts = alerts
self.attribution = try await service.attribution
} catch {
self.error = error
}
isLoading = false
}
}import SwiftUI
import WeatherKit
struct WeatherDashboardView: View {
@Environment(WeatherManager.self) private var manager
let location: CLLocation
var body: some View {
NavigationStack {
ScrollView {
VStack {
if manager.isLoading {
ProgressView("Loading weather...")
} else if let current = manager.current {
currentConditionsCard(current)
}
if let hourly = manager.hourlyForecast {
hourlyForecastSection(hourly)
}
if let daily = manager.dailyForecast {
dailyForecastSection(daily)
}
if let alerts = manager.alerts, !alerts.isEmpty {
alertsSection(alerts)
}
if let attribution = manager.attribution {
WeatherAttributionView(attribution: attribution)
}
}
.padding()
}
.navigationTitle("Weather")
.task {
await manager.fetchWeather(for: location)
}
.refreshable {
await manager.fetchWeather(for: location)
}
}
}
private func currentConditionsCard(_ current: CurrentWeather) -> some View {
VStack {
Image(systemName: current.symbolName)
.font(.system(size: 60))
.symbolRenderingMode(.multicolor)
Text(current.temperature.formatted())
.font(.system(size: 48, weight: .thin))
Text(current.condition.description)
.font(.title3)
.foregroundStyle(.secondary)
HStack {
Label(
"Humidity \(current.humidity.formatted(.percent))",
systemImage: "humidity"
)
Label(
"Wind \(current.wind.speed.formatted())",
systemImage: "wind"
)
Label(
"UV \(current.uvIndex.value)",
systemImage: "sun.max"
)
}
.font(.caption)
}
.padding()
}
private func hourlyForecastSection(_ forecast: Forecast<HourWeather>) -> some View {
VStack(alignment: .leading) {
Text("Hourly Forecast")
.font(.headline)
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(Array(forecast.prefix(12)), id: \.date) { hour in
VStack {
Text(hour.date, format: .dateTime.hour())
.font(.caption)
Image(systemName: hour.symbolName)
.symbolRenderingMode(.multicolor)
Text(hour.temperature.formatted())
.font(.subheadline)
}
}
}
}
}
}
private func dailyForecastSection(_ forecast: Forecast<DayWeather>) -> some View {
VStack(alignment: .leading) {
Text("10-Day Forecast")
.font(.headline)
ForEach(Array(forecast), id: \.date) { day in
HStack {
Text(day.date, format: .dateTime.weekday(.abbreviated))
.frame(width: 40, alignment: .leading)
Image(systemName: day.symbolName)
.symbolRenderingMode(.multicolor)
.frame(width: 30)
Text(day.lowTemperature.formatted())
.foregroundStyle(.secondary)
.frame(width: 50, alignment: .trailing)
temperatureBar(low: day.lowTemperature, high: day.highTemperature)
Text(day.highTemperature.formatted())
.frame(width: 50)
}
.font(.subheadline)
}
}
}
private func temperatureBar(
low: Measurement<UnitTemperature>,
high: Measurement<UnitTemperature>
) -> some View {
Capsule()
.fill(.linearGradient(
colors: [.blue, .orange],
startPoint: .leading,
endPoint: .trailing
))
.frame(height: 4)
.containerRelativeFrame(.horizontal) { length, _ in
length * 0.3
}
}
private func alertsSection(_ alerts: [WeatherAlert]) -> some View {
VStack(alignment: .leading) {
Text("Weather Alerts")
.font(.headline)
ForEach(alerts, id: \.detailsURL) { alert in
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(alert.severity == .extreme ? .red : .orange)
VStack(alignment: .leading) {
Text(alert.summary)
.font(.subheadline)
Text(alert.region)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding()
.background(.yellow.opacity(0.1))
.clipShape(.rect(cornerRadius: 8))
}
}
}
}struct WeatherAttributionView: View {
let attribution: WeatherAttribution
@Environment(\.colorScheme) private var colorScheme
var body: some View {
VStack {
AsyncImage(url: markURL) { image in
image
.resizable()
.scaledToFit()
.frame(height: 12)
} placeholder: {
Text(attribution.serviceName)
.font(.caption2)
}
Link(destination: attribution.legalPageURL) {
Text("Data Sources")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
.padding(.vertical)
}
private var markURL: URL {
colorScheme == .dark
? attribution.combinedMarkDarkURL
: attribution.combinedMarkLightURL
}
}import SwiftUI
import Charts
import WeatherKit
struct HourlyTemperatureChart: View {
let forecast: Forecast<HourWeather>
var body: some View {
Chart(Array(forecast.prefix(24)), id: \.date) { hour in
LineMark(
x: .value("Hour", hour.date),
y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
)
.interpolationMethod(.catmullRom)
.foregroundStyle(.orange)
AreaMark(
x: .value("Hour", hour.date),
y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
)
.interpolationMethod(.catmullRom)
.foregroundStyle(.orange.opacity(0.1))
}
.chartYAxisLabel("Temperature (C)")
.chartXAxis {
AxisMarks(values: .stride(by: .hour, count: 3)) { _ in
AxisGridLine()
AxisValueLabel(format: .dateTime.hour())
}
}
.frame(height: 200)
}
}struct DailyPrecipitationChart: View {
let forecast: Forecast<DayWeather>
var body: some View {
Chart(Array(forecast), id: \.date) { day in
BarMark(
x: .value("Day", day.date, unit: .day),
y: .value("Chance", day.precipitationChance)
)
.foregroundStyle(.blue.gradient)
}
.chartYScale(domain: 0...1)
.chartYAxis {
AxisMarks(format: .percent)
}
.chartXAxis {
AxisMarks(values: .stride(by: .day)) { _ in
AxisGridLine()
AxisValueLabel(format: .dateTime.weekday(.abbreviated))
}
}
.frame(height: 150)
}
}WeatherKit provides historical weather data through daily and monthly statistics.
func fetchDailyStats(
for location: CLLocation,
dateRange: DateInterval
) async throws {
let stats = try await WeatherService.shared.dailyStatistics(
for: location,
forDaysIn: dateRange,
including: [.temperature, .precipitation]
)
for dayStat in stats {
print("Date: \(dayStat.date)")
if let temp = dayStat.statistics(for: .temperature) {
print(" Avg temp: \(temp.mean?.formatted() ?? "N/A")")
print(" Min temp: \(temp.minimum?.formatted() ?? "N/A")")
print(" Max temp: \(temp.maximum?.formatted() ?? "N/A")")
}
}
}func fetchMonthlyStats(for location: CLLocation) async throws {
let stats = try await WeatherService.shared.monthlyStatistics(
for: location,
including: [.temperature, .precipitation]
)
for monthStat in stats {
print("Month: \(monthStat.month)")
}
}extension WeatherCondition {
var themeColor: Color {
switch self {
case .clear, .mostlyClear:
return .yellow
case .partlyCloudy, .mostlyCloudy, .cloudy:
return .gray
case .rain, .heavyRain, .drizzle:
return .blue
case .snow, .heavySnow, .flurries, .sleet, .freezingRain,
.freezingDrizzle, .wintryMix, .blizzard:
return .cyan
case .thunderstorms, .strongStorms, .tropicalStorm, .hurricane:
return .purple
case .foggy, .haze, .smoky:
return .gray.opacity(0.6)
case .breezy, .windy:
return .teal
case .hot:
return .red
case .frigid, .blowingDust:
return .indigo
@unknown default:
return .primary
}
}
}extension WeatherSeverity {
var displayPriority: Int {
switch self {
case .extreme:
return 4
case .severe:
return 3
case .moderate:
return 2
case .minor:
return 1
case .unknown:
return 0
@unknown default:
return 0
}
}
}actor WeatherCache {
struct CacheEntry {
let weather: CurrentWeather
let hourly: Forecast<HourWeather>
let daily: Forecast<DayWeather>
let fetchDate: Date
}
private var cache: [String: CacheEntry] = [:]
private let staleness: TimeInterval
init(staleness: TimeInterval = 600) { // 10 minutes default
self.staleness = staleness
}
func get(for key: String) -> CacheEntry? {
guard let entry = cache[key],
Date.now.timeIntervalSince(entry.fetchDate) < staleness else {
cache[key] = nil
return nil
}
return entry
}
func set(_ entry: CacheEntry, for key: String) {
cache[key] = entry
}
/// Generate a cache key from a location (rounded to ~1km precision)
static func key(for location: CLLocation) -> String {
let lat = (location.coordinate.latitude * 100).rounded() / 100
let lon = (location.coordinate.longitude * 100).rounded() / 100
return "\(lat),\(lon)"
}
}@Observable
@MainActor
final class CachedWeatherManager {
private let service = WeatherService.shared
private let cache = WeatherCache()
var current: CurrentWeather?
func fetchWeather(for location: CLLocation) async throws {
let key = WeatherCache.key(for: location)
if let cached = await cache.get(for: key) {
current = cached.weather
return
}
let (current, hourly, daily) = try await service.weather(
for: location,
including: .current, .hourly, .daily
)
let entry = WeatherCache.CacheEntry(
weather: current,
hourly: hourly,
daily: daily,
fetchDate: .now
)
await cache.set(entry, for: key)
self.current = current
}
}import CoreLocation
import WeatherKit
@Observable
@MainActor
final class LocationWeatherManager: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private let weatherService = WeatherService.shared
var current: CurrentWeather?
var locationError: Error?
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
}
func requestWeather() {
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
nonisolated func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.last else { return }
Task { @MainActor in
do {
current = try await weatherService.weather(
for: location,
including: .current
)
} catch {
locationError = error
}
}
}
nonisolated func locationManager(
_ manager: CLLocationManager,
didFailWithError error: Error
) {
Task { @MainActor in
locationError = error
}
}
}skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references