🌦 WeatherKit 완전정볡

Apple 곡식 날씨 API둜 μ •ν™•ν•œ 날씨 정보λ₯Ό μ œκ³΅ν•˜μ„Έμš”. ν˜„μž¬ 날씨뢀터 10일 μ˜ˆλ³΄κΉŒμ§€!

✨ WeatherKitμ΄λž€?

WeatherKit은 Apple이 μ œκ³΅ν•˜λŠ” 곡식 날씨 APIμž…λ‹ˆλ‹€. iOS 16+μ—μ„œ μ‚¬μš© κ°€λŠ₯ν•˜λ©°, μ›” 50만 κ±΄κΉŒμ§€ λ¬΄λ£Œμž…λ‹ˆλ‹€.

πŸ”‘ μ„€μ • (Apple Developer)

Setup Steps
1. Apple Developer μ½˜μ†” 접속
2. Certificates, Identifiers & Profiles
3. Keys νƒ­μ—μ„œ μƒˆ ν‚€ 생성
4. "WeatherKit" μ²΄ν¬λ°•μŠ€ ν™œμ„±ν™”
5. λ‹€μš΄λ‘œλ“œν•œ .p8 파일 μ•ˆμ „ν•˜κ²Œ 보관

⚠️ ν‚€λŠ” ν•œ 번만 λ‹€μš΄λ‘œλ“œ κ°€λŠ₯ν•˜λ―€λ‘œ λ°±μ—… ν•„μˆ˜!

🌑️ ν˜„μž¬ 날씨 κ°€μ Έμ˜€κΈ°

CurrentWeather.swift
import WeatherKit
import CoreLocation

let weatherService = WeatherService()

func fetchCurrentWeather(for location: CLLocation) async throws -> CurrentWeather {
    let weather = try await weatherService.weather(for: location)
    return weather.currentWeather
}

// μ‚¬μš© μ˜ˆμ‹œ
Task {
    let location = CLLocation(latitude: 37.5665, longitude: 126.9780)  // μ„œμšΈ
    let current = try await fetchCurrentWeather(for: location)

    print("μ˜¨λ„: \(current.temperature.value)Β°C")
    print("체감 μ˜¨λ„: \(current.apparentTemperature.value)Β°C")
    print("μŠ΅λ„: \(current.humidity * 100)%")
    print("날씨: \(current.condition.description)")
    print("λ°”λžŒ: \(current.wind.speed.value) m/s")
}

πŸ“… μ‹œκ°„λ³„ 예보 (24μ‹œκ°„)

HourlyForecast.swift
func fetchHourlyForecast(for location: CLLocation) async throws -> Forecast<HourWeather> {
    let weather = try await weatherService.weather(for: location)
    return weather.hourlyForecast
}

// μ‚¬μš© μ˜ˆμ‹œ
Task {
    let location = CLLocation(latitude: 37.5665, longitude: 126.9780)
    let hourly = try await fetchHourlyForecast(for: location)

    // λ‹€μŒ 12μ‹œκ°„ 예보
    for hour in hourly.prefix(12) {
        print("\(hour.date) - \(hour.temperature.value)Β°C - \(hour.condition)")
    }
}

πŸ“† 일별 예보 (10일)

DailyForecast.swift
func fetchDailyForecast(for location: CLLocation) async throws -> Forecast<DayWeather> {
    let weather = try await weatherService.weather(for: location)
    return weather.dailyForecast
}

// μ‚¬μš© μ˜ˆμ‹œ
Task {
    let location = CLLocation(latitude: 37.5665, longitude: 126.9780)
    let daily = try await fetchDailyForecast(for: location)

    for day in daily {
        print("\(day.date)")
        print("졜고: \(day.highTemperature.value)°C")
        print("μ΅œμ €: \(day.lowTemperature.value)Β°C")
        print("κ°•μˆ˜ ν™•λ₯ : \(day.precipitationChance * 100)%")
        print("일좜: \(day.sun.sunrise!)")
        print("일λͺ°: \(day.sun.sunset!)")
    }
}

β˜” 뢄별 κ°•μˆ˜ 예보 (λ‹€μŒ 1μ‹œκ°„)

MinuteForecast.swift
func fetchMinuteForecast(for location: CLLocation) async throws -> Forecast<MinuteWeather>? {
    let weather = try await weatherService.weather(for: location)
    return weather.minuteForecast
}

// μ‚¬μš© μ˜ˆμ‹œ (미ꡭ만 지원)
Task {
    let location = CLLocation(latitude: 37.7749, longitude: -122.4194)  // μƒŒν”„λž€μ‹œμŠ€μ½”
    if let minute = try await fetchMinuteForecast(for: location) {
        for m in minute {
            print("\(m.date) - κ°•μˆ˜ 강도: \(m.precipitation.value)")
        }
    } else {
        print("뢄별 예보 지원 μ•ˆ 함 (미ꡭ만 κ°€λŠ₯)")
    }
}

🚨 기상 경보

WeatherAlerts.swift
func fetchWeatherAlerts(for location: CLLocation) async throws -> [WeatherAlert]? {
    let weather = try await weatherService.weather(for: location)
    return weather.weatherAlerts
}

// μ‚¬μš© μ˜ˆμ‹œ
Task {
    let location = CLLocation(latitude: 37.5665, longitude: 126.9780)
    if let alerts = try await fetchWeatherAlerts(for: location) {
        for alert in alerts {
            print("⚠️ \(alert.summary)")
            print("경보 μˆ˜μ€€: \(alert.severity)")
            print("발효: \(alert.effectiveTime)")
            print("만료: \(alert.expireTime)")
        }
    }
}

🌀 날씨 μƒνƒœ (WeatherCondition)

WeatherCondition.swift
import WeatherKit

func getWeatherIcon(_ condition: WeatherCondition) -> String {
    switch condition {
    case .clear:
        return "sun.max.fill"
    case .cloudy:
        return "cloud.fill"
    case .rain:
        return "cloud.rain.fill"
    case .snow:
        return "snow"
    case .sleet:
        return "cloud.sleet.fill"
    case .hail:
        return "cloud.hail.fill"
    case .thunderstorms:
        return "cloud.bolt.fill"
    case .heavyRain:
        return "cloud.heavyrain.fill"
    case .strongStorms:
        return "cloud.bolt.rain.fill"
    case .foggy:
        return "cloud.fog.fill"
    case .windy:
        return "wind"
    @unknown default:
        return "questionmark"
    }
}

// ν•œκΈ€ μ„€λͺ…
func getWeatherDescription(_ condition: WeatherCondition) -> String {
    switch condition {
    case .clear: return "λ§‘μŒ"
    case .cloudy: return "흐림"
    case .rain: return "λΉ„"
    case .snow: return "눈"
    case .thunderstorms: return "λ‡Œμš°"
    @unknown default: return "μ•Œ 수 μ—†μŒ"
    }
}

πŸ“± SwiftUI 톡합

WeatherView.swift
import SwiftUI
import WeatherKit
import CoreLocation

@Observable
class WeatherViewModel {
    var currentWeather: CurrentWeather?
    var hourlyForecast: Forecast<HourWeather>?
    var isLoading = false

    let weatherService = WeatherService()

    func loadWeather(for location: CLLocation) async {
        isLoading = true
        defer { isLoading = false }

        do {
            let weather = try await weatherService.weather(for: location)
            currentWeather = weather.currentWeather
            hourlyForecast = weather.hourlyForecast
        } catch {
            print("날씨 λ‘œλ“œ μ‹€νŒ¨: \(error)")
        }
    }
}

struct WeatherView: View {
    var viewModel = WeatherViewModel()
    let location = CLLocation(latitude: 37.5665, longitude: 126.9780)

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                if viewModel.isLoading {
                    ProgressView()
                } else if let current = viewModel.currentWeather {
                    // ν˜„μž¬ 날씨
                    VStack {
                        Image(systemName: getWeatherIcon(current.condition))
                            .font(.system(size: 80))
                            .symbolRenderingMode(.multicolor)

                        Text("\(Int(current.temperature.value))Β°")
                            .font(.system(size: 72, weight: .thin))

                        Text(getWeatherDescription(current.condition))
                            .font(.title2)
                    }

                    // μ‹œκ°„λ³„ 예보
                    if let hourly = viewModel.hourlyForecast {
                        ScrollView(.horizontal, showsIndicators: false) {
                            HStack(spacing: 16) {
                                ForEach(hourly.prefix(24), id: \.date) { hour in
                                    VStack {
                                        Text(hour.date, format: .dateTime.hour())
                                            .font(.caption)
                                        Image(systemName: getWeatherIcon(hour.condition))
                                            .font(.title2)
                                        Text("\(Int(hour.temperature.value))Β°")
                                            .font(.headline)
                                    }
                                    .frame(width: 60)
                                }
                            }
                            .padding()
                        }
                    }
                }
            }
        }
        .task {
            await viewModel.loadWeather(for: location)
        }
    }
}

⚑ μ—λŸ¬ 처리

ErrorHandling.swift
do {
    let weather = try await weatherService.weather(for: location)
} catch {
    if let weatherError = error as? WeatherError {
        switch weatherError {
        case .locationUnavailable:
            print("μœ„μΉ˜ 정보 μ—†μŒ")
        case .locationOutOfRange:
            print("μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ§€μ—­")
        case .networkUnavailable:
            print("λ„€νŠΈμ›Œν¬ μ—°κ²° μ—†μŒ")
        @unknown default:
            print("μ•Œ 수 μ—†λŠ” μ—λŸ¬")
        }
    }
}

πŸ’° μ‚¬μš©λŸ‰ μ œν•œ

Rate Limits
WeatherKit API μ œν•œ:
- μ›” 50만 건 무료
- 초과 μ‹œ: $0.0001/call (100만 건당 $100)
- Rate limit: λΆ„λ‹Ή 500 requests

⚠️ 캐싱을 적극 ν™œμš©ν•˜μ„Έμš”!
- ν˜„μž¬ 날씨: 10λΆ„ μΊμ‹œ
- μ‹œκ°„λ³„ 예보: 30λΆ„ μΊμ‹œ
- 일별 예보: 1μ‹œκ°„ μΊμ‹œ

πŸ’‘ HIG 체크리슀트
βœ… Apple 제곡 날씨 μ•„μ΄μ½˜ μ‚¬μš© (SF Symbols)
βœ… 데이터 좜처 ν‘œμ‹œ ("Weather data provided by Apple")
βœ… μ μ ˆν•œ μΊμ‹±μœΌλ‘œ API 호좜 μ΅œμ†Œν™”
βœ… λ‘œλ”© μƒνƒœ ν‘œμ‹œ
βœ… λ„€νŠΈμ›Œν¬ μ—λŸ¬ 처리

πŸ“¦ ν•™μŠ΅ 자료

πŸ’»
GitHub ν”„λ‘œμ νŠΈ
🍎
Apple 곡식 λ¬Έμ„œ
πŸŽ₯
WWDC22 μ„Έμ…˜