🌐 KO

πŸ”‹ EnergyKit

⭐ Difficulty: ⭐⭐ ⏱️ Est. Time: 1-2h πŸ“‚ iOS 26

κ°€μ •μ˜ μ—λ„ˆμ§€ μ‚¬μš©μ„ μΆ”μ ν•˜κ³  μ΅œμ ν™”ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬

iOS 18+πŸ†• 2024

✨ EnergyKit is?

EnergyKit is a framework introduced in iOS 18 that tracks and analyzes home energy usage in real-time. Integrated with HomeKit, it collects data from smart plugs, power meters, solar panels and other energy devices, providing users with power consumption patterns, renewable energy production, and saving tips. An energy management solution that supports both environmental protection and electricity cost reduction.

πŸ’‘ Key Features: Real-Time Power Tracking Β· Renewable Energy Monitoring Β· Per-Device Usage Β· Pattern Prediction Β· HomeKit Integration Β· Energy Saving Tips Β· Carbon Footprint

🎯 1. Basic Setup

EnergyKit을 μ΄ˆκΈ°ν™”ν•˜κ³  κΆŒν•œμ„ μš”μ²­.

EnergyManager.swift β€” Basic Setup
import SwiftUI
import HomeKit

// μ—λ„ˆμ§€ κ΄€λ¦¬μž
@Observable
class EnergyManager: NSObject {
    var homeManager = HMHomeManager()
    var currentHome: HMHome?
    var energyDevices: [EnergyDevice] = []
    var isAuthorized = false

    override init() {
        super.init()
        homeManager.delegate = self
    }

    // HomeKit κΆŒν•œ 확인
    func checkAuthorization() async -> Bool {
        // HomeKit은 μžλ™μœΌλ‘œ κΆŒν•œ μš”μ²­
        await MainActor.run {
            isAuthorized = homeManager.authorizationStatus == .authorized
        }
        return isAuthorized
    }

    // μ—λ„ˆμ§€ κΈ°κΈ° 검색
    func discoverEnergyDevices() {
        guard let home = homeManager.primaryHome else { return }

        currentHome = home
        energyDevices = []

        // μ „λ ₯ μΈ‘μ • κΈ°λŠ₯이 μžˆλŠ” κΈ°κΈ° 필터링
        for accessory in home.accessories {
            for service in accessory.services {
                // μ „λ ₯ μΈ‘μ • νŠΉμ„± 확인
                if service.characteristics.contains(where: {
                    $0.characteristicType == HMCharacteristicTypePowerState
                }) {
                    let device = EnergyDevice(
                        id: accessory.uniqueIdentifier,
                        name: accessory.name,
                        room: accessory.room?.name ?? "μ•Œ 수 μ—†μŒ",
                        accessory: accessory
                    )
                    energyDevices.append(device)
                }
            }
        }
    }
}

// HomeKit 델리게이트
extension EnergyManager: HMHomeManagerDelegate {
    func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
        discoverEnergyDevices()
    }
}

// μ—λ„ˆμ§€ κΈ°κΈ° λͺ¨λΈ
struct EnergyDevice: Identifiable {
    let id: UUID
    let name: String
    let room: String
    let accessory: HMAccessory
    var currentPower: Double = 0 // Watts
    var todayEnergy: Double = 0 // kWh
}

⚑ 2. μ‹€μ‹œκ°„ μ „λ ₯ λͺ¨λ‹ˆν„°λ§

ν˜„μž¬ μ „λ ₯ μ†ŒλΉ„λŸ‰μ„ μ‹€μ‹œκ°„μœΌλ‘œ 확인.

RealTimePowerView.swift β€” Real-Time Monitoring
import SwiftUI
import Charts

struct PowerReading: Identifiable {
    let id = UUID()
    let timestamp: Date
    let power: Double // Watts
}

@Observable
class PowerMonitor {
    var currentPower: Double = 0
    var readings: [PowerReading] = []
    private var timer: Timer?

    // μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ μ‹œμž‘
    func startMonitoring() {
        timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { _ in
            Task { @MainActor in
                await self.updatePowerReading()
            }
        }
    }

    // λͺ¨λ‹ˆν„°λ§ 쀑지
    func stopMonitoring() {
        timer?.invalidate()
        timer = nil
    }

    // μ „λ ₯ μΈ‘μ •
    func updatePowerReading() async {
        // μ‹€μ œλ‘œλŠ” HomeKitμ—μ„œ 데이터 읽기
        let power = Double.random(in: 500...2000)

        currentPower = power
        readings.append(PowerReading(timestamp: Date(), power: power))

        // 졜근 1μ‹œκ°„ λ°μ΄ν„°λ§Œ μœ μ§€
        let oneHourAgo = Date().addingTimeInterval(-3600)
        readings.removeAll { $0.timestamp < oneHourAgo }
    }

    var averagePower: Double {
        guard !readings.isEmpty else { return 0 }
        return readings.map { $0.power }.reduce(0, +) / Double(readings.count)
    }
}

struct RealTimePowerView: View {
    @State private var monitor = PowerMonitor()

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // ν˜„μž¬ μ „λ ₯
                VStack {
                    Text("ν˜„μž¬ μ‚¬μš©λŸ‰")
                        .font(.headline)
                        .foregroundStyle(.secondary)

                    HStack(alignment: .firstTextBaseline, spacing: 4) {
                        Text("\(Int(monitor.currentPower))")
                            .font(.system(size: 60, weight: .bold))
                            .contentTransition(.numericText())

                        Text("W")
                            .font(.title)
                            .foregroundStyle(.secondary)
                    }

                    Text("평균 \(Int(monitor.averagePower))W")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.blue.opacity(0.1))
                .cornerRadius(16)

                // μ‹€μ‹œκ°„ 차트
                VStack(alignment: .leading) {
                    Text("졜근 1μ‹œκ°„")
                        .font(.headline)

                    Chart(monitor.readings) { reading in
                        LineMark(
                            x: .value("μ‹œκ°„", reading.timestamp),
                            y: .value("μ „λ ₯", reading.power)
                        )
                        .foregroundStyle(.blue)
                        .interpolationMethod(.catmullRom)

                        AreaMark(
                            x: .value("μ‹œκ°„", reading.timestamp),
                            y: .value("μ „λ ₯", reading.power)
                        )
                        .foregroundStyle(.blue.opacity(0.2))
                        .interpolationMethod(.catmullRom)
                    }
                    .frame(height: 200)
                    .chartYAxis {
                        AxisMarks { value in
                            AxisValueLabel {
                                if let power = value.as(Double.self) {
                                    Text("\(Int(power))W")
                                }
                            }
                        }
                    }
                }

                // λΉ„μš© μΆ”μ •
                HStack {
                    VStack(alignment: .leading) {
                        Text("μ‹œκ°„λ‹Ή λΉ„μš©")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(estimatedHourlyCost)원")
                            .font(.title3)
                            .fontWeight(.semibold)
                    }

                    Spacer()

                    VStack(alignment: .trailing) {
                        Text("μ›” μ˜ˆμƒ λΉ„μš©")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(estimatedMonthlyCost)원")
                            .font(.title3)
                            .fontWeight(.semibold)
                    }
                }
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("μ „λ ₯ λͺ¨λ‹ˆν„°")
        .onAppear {
            monitor.startMonitoring()
        }
        .onDisappear {
            monitor.stopMonitoring()
        }
    }

    // μ „κΈ° μš”κΈˆ 계산 (kWhλ‹Ή 300원 κ°€μ •)
    var estimatedHourlyCost: Int {
        Int(monitor.currentPower / 1000 * 300)
    }

    var estimatedMonthlyCost: Int {
        estimatedHourlyCost * 24 * 30
    }
}

πŸ“Š 3. 기기별 μ‚¬μš©λŸ‰ 뢄석

각 기기의 μ—λ„ˆμ§€ μ†ŒλΉ„λ₯Ό λΆ„μ„ν•˜κ³  비ꡐ.

DeviceAnalysisView.swift β€” Per-Device Analysis
import SwiftUI
import Charts

struct DeviceEnergyData: Identifiable {
    let id = UUID()
    let deviceName: String
    let energy: Double // kWh
    let percentage: Double
    let category: DeviceCategory
}

enum DeviceCategory: String, CaseIterable {
    case lighting = "μ‘°λͺ…"
    case hvac = "λƒ‰λ‚œλ°©"
    case appliance = "κ°€μ „μ œν’ˆ"
    case entertainment = "μ—”ν„°ν…ŒμΈλ¨ΌνŠΈ"
    case other = "기타"

    var color: Color {
        switch self {
        case .lighting: return .yellow
        case .hvac: return .blue
        case .appliance: return .green
        case .entertainment: return .purple
        case .other: return .gray
        }
    }
}

struct DeviceAnalysisView: View {
    @State private var devices: [DeviceEnergyData] = [
        .init(deviceName: "κ±°μ‹€ 에어컨", energy: 45.2, percentage: 35, category: .hvac),
        .init(deviceName: "냉μž₯κ³ ", energy: 28.5, percentage: 22, category: .appliance),
        .init(deviceName: "μ‘°λͺ…", energy: 18.7, percentage: 15, category: .lighting),
        .init(deviceName: "TV", energy: 15.3, percentage: 12, category: .entertainment),
        .init(deviceName: "세탁기", energy: 12.8, percentage: 10, category: .appliance),
        .init(deviceName: "기타", energy: 8.5, percentage: 6, category: .other)
    ]

    var totalEnergy: Double {
        devices.map { $0.energy }.reduce(0, +)
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // 총 μ‚¬μš©λŸ‰
                VStack {
                    Text("이번 달 총 μ‚¬μš©λŸ‰")
                        .font(.headline)
                        .foregroundStyle(.secondary)

                    HStack(alignment: .firstTextBaseline) {
                        Text("\(totalEnergy, specifier: "%.1f")")
                            .font(.system(size: 48, weight: .bold))
                        Text("kWh")
                            .font(.title2)
                            .foregroundStyle(.secondary)
                    }
                }

                // μ›ν˜• 차트
                Chart(devices) { device in
                    SectorMark(
                        angle: .value("μ—λ„ˆμ§€", device.energy),
                        innerRadius: .ratio(0.6),
                        angularInset: 2
                    )
                    .foregroundStyle(device.category.color)
                    .annotation(position: .overlay) {
                        Text("\(Int(device.percentage))%")
                            .font(.caption)
                            .fontWeight(.bold)
                    }
                }
                .frame(height: 250)

                // κΈ°κΈ° λͺ©λ‘
                VStack(alignment: .leading, spacing: 12) {
                    Text("기기별 상세")
                        .font(.headline)

                    ForEach(devices) { device in
                        HStack {
                            Circle()
                                .fill(device.category.color)
                                .frame(width: 12, height: 12)

                            VStack(alignment: .leading, spacing: 2) {
                                Text(device.deviceName)
                                    .font(.subheadline)
                                Text(device.category.rawValue)
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                            }

                            Spacer()

                            VStack(alignment: .trailing, spacing: 2) {
                                Text("\(device.energy, specifier: "%.1f") kWh")
                                    .font(.subheadline)
                                    .fontWeight(.semibold)

                                Text("\(Int(device.percentage))%")
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                            }
                        }
                        .padding()
                        .background(Color.gray.opacity(0.05))
                        .cornerRadius(8)
                    }
                }

                // μ ˆμ•½ 팁
                VStack(alignment: .leading, spacing: 12) {
                    Label("μ ˆμ•½ 팁", systemImage: "lightbulb.fill")
                        .font(.headline)
                        .foregroundStyle(.orange)

                    Text("에어컨 μ„€μ • μ˜¨λ„λ₯Ό 1도 올리면 μ›” μ•½ 6% μ ˆμ•½ν•  수 μžˆμŠ΅λ‹ˆλ‹€.")
                        .font(.subheadline)
                }
                .padding()
                .background(Color.orange.opacity(0.1))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("기기별 μ‚¬μš©λŸ‰")
    }
}

β˜€οΈ 4. μž¬μƒ μ—λ„ˆμ§€ λͺ¨λ‹ˆν„°λ§

νƒœμ–‘κ΄‘ νŒ¨λ„ λ“± μž¬μƒ μ—λ„ˆμ§€ μƒμ‚°λŸ‰μ„ 좔적.

SolarEnergyView.swift β€” Renewable Energy
import SwiftUI
import Charts

struct SolarData: Identifiable {
    let id = UUID()
    let hour: Int
    let production: Double // kWh
    let consumption: Double // kWh
}

@Observable
class SolarEnergyManager {
    var todayProduction: Double = 18.5
    var todayConsumption: Double = 22.3
    var gridExport: Double = 3.2 // νŒλ§€λŸ‰
    var gridImport: Double = 7.0 // κ΅¬λ§€λŸ‰

    var hourlyData: [SolarData] = [
        .init(hour: 6, production: 0.2, consumption: 0.8),
        .init(hour: 7, production: 0.8, consumption: 1.2),
        .init(hour: 8, production: 1.5, consumption: 1.0),
        .init(hour: 9, production: 2.3, consumption: 0.9),
        .init(hour: 10, production: 3.0, consumption: 0.8),
        .init(hour: 11, production: 3.5, consumption: 1.0),
        .init(hour: 12, production: 3.8, consumption: 1.2),
        .init(hour: 13, production: 3.4, consumption: 1.5),
        .init(hour: 14, production: 2.8, consumption: 1.3),
        .init(hour: 15, production: 2.0, consumption: 1.8),
        .init(hour: 16, production: 1.2, consumption: 2.5),
        .init(hour: 17, production: 0.5, consumption: 3.2)
    ]

    var selfSufficiencyRate: Double {
        (todayProduction / todayConsumption) * 100
    }
}

struct SolarEnergyView: View {
    @State private var manager = SolarEnergyManager()

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // 였늘 μš”μ•½
                HStack(spacing: 12) {
                    VStack {
                        Image(systemName: "sun.max.fill")
                            .font(.title)
                            .foregroundStyle(.orange)

                        Text("생산")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(manager.todayProduction, specifier: "%.1f") kWh")
                            .font(.headline)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.orange.opacity(0.1))
                    .cornerRadius(12)

                    VStack {
                        Image(systemName: "bolt.fill")
                            .font(.title)
                            .foregroundStyle(.blue)

                        Text("μ‚¬μš©")
                            .font(.caption)
                            .foregroundStyle(.secondary)

                        Text("\(manager.todayConsumption, specifier: "%.1f") kWh")
                            .font(.headline)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(12)
                }

                // μžκΈ‰λ₯ 
                VStack {
                    Text("μ—λ„ˆμ§€ μžκΈ‰λ₯ ")
                        .font(.headline)

                    ZStack {
                        Circle()
                            .stroke(Color.gray.opacity(0.2), lineWidth: 20)

                        Circle()
                            .trim(from: 0, to: manager.selfSufficiencyRate / 100)
                            .stroke(
                                LinearGradient(
                                    colors: [.green, .yellow],
                                    startPoint: .leading,
                                    endPoint: .trailing
                                ),
                                style: StrokeStyle(lineWidth: 20, lineCap: .round)
                            )
                            .rotationEffect(.degrees(-90))
                            .animation(.easeInOut, value: manager.selfSufficiencyRate)

                        VStack {
                            Text("\(Int(manager.selfSufficiencyRate))%")
                                .font(.system(size: 42, weight: .bold))
                            Text("μžκΈ‰λ₯ ")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                    .frame(width: 200, height: 200)
                }

                // μ‹œκ°„λ³„ 차트
                VStack(alignment: .leading) {
                    Text("μ‹œκ°„λ³„ 생산/μ†ŒλΉ„")
                        .font(.headline)

                    Chart {
                        ForEach(manager.hourlyData) { data in
                            // μƒμ‚°λŸ‰
                            BarMark(
                                x: .value("μ‹œκ°„", data.hour),
                                y: .value("생산", data.production)
                            )
                            .foregroundStyle(.orange)
                            .position(by: .value("νƒ€μž…", "생산"))

                            // μ†ŒλΉ„λŸ‰
                            BarMark(
                                x: .value("μ‹œκ°„", data.hour),
                                y: .value("μ†ŒλΉ„", data.consumption)
                            )
                            .foregroundStyle(.blue)
                            .position(by: .value("νƒ€μž…", "μ†ŒλΉ„"))
                        }
                    }
                    .frame(height: 200)
                    .chartXAxis {
                        AxisMarks { value in
                            AxisValueLabel {
                                if let hour = value.as(Int.self) {
                                    Text("\(hour)μ‹œ")
                                }
                            }
                        }
                    }
                }

                // μ „λ ₯망 거래
                VStack(alignment: .leading, spacing: 12) {
                    Text("μ „λ ₯망 거래")
                        .font(.headline)

                    HStack {
                        Label("판맀", systemImage: "arrow.up.circle.fill")
                            .foregroundStyle(.green)
                        Spacer()
                        Text("\(manager.gridExport, specifier: "%.1f") kWh")
                            .fontWeight(.semibold)
                    }

                    HStack {
                        Label("ꡬ맀", systemImage: "arrow.down.circle.fill")
                            .foregroundStyle(.red)
                        Spacer()
                        Text("\(manager.gridImport, specifier: "%.1f") kWh")
                            .fontWeight(.semibold)
                    }
                }
                .padding()
                .background(Color.gray.opacity(0.05))
                .cornerRadius(12)

                // ν™˜κ²½ 영ν–₯
                VStack(alignment: .leading, spacing: 8) {
                    Label("ν™˜κ²½ 기여도", systemImage: "leaf.fill")
                        .font(.headline)
                        .foregroundStyle(.green)

                    Text("였늘 \(carbonReduction)kg의 COβ‚‚ λ°°μΆœμ„ μ€„μ˜€μŠ΅λ‹ˆλ‹€")
                        .font(.subheadline)

                    Text("λ‚˜λ¬΄ \(treesEquivalent)그루 심은 효과")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.green.opacity(0.1))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("νƒœμ–‘κ΄‘ μ—λ„ˆμ§€")
    }

    // CO2 μ ˆκ°λŸ‰ (kWhλ‹Ή μ•½ 0.46kg)
    var carbonReduction: Double {
        manager.todayProduction * 0.46
    }

    // λ‚˜λ¬΄ ν™˜μ‚° (λ‚˜λ¬΄ 1κ·Έλ£¨λŠ” μ—°κ°„ μ•½ 6kg CO2 흑수)
    var treesEquivalent: Int {
        Int(carbonReduction / 6 * 365)
    }
}

πŸ“ˆ 5. μ‚¬μš© νŒ¨ν„΄ 뢄석

Analyze energy usage patterns and provide optimization suggestions.

UsagePatternView.swift β€” Pattern Analysis
import SwiftUI
import Charts

struct DailyUsage: Identifiable {
    let id = UUID()
    let date: Date
    let energy: Double
    let cost: Double
}

@Observable
class UsagePatternAnalyzer {
    var weeklyData: [DailyUsage] = []
    var peakHour: Int = 19 // μ˜€ν›„ 7μ‹œ
    var recommendations: [String] = []

    init() {
        generateWeeklyData()
        analyzePattern()
    }

    func generateWeeklyData() {
        let calendar = Calendar.current
        for day in 0.<7 {
            let date = calendar.date(byAdding: .day, value: -day, to: Date())!
            let energy = Double.random(in: 15...30)
            let cost = energy * 300 // kWhλ‹Ή 300원

            weeklyData.insert(
                DailyUsage(date: date, energy: energy, cost: cost),
                at: 0
            )
        }
    }

    func analyzePattern() {
        // νŒ¨ν„΄ 뢄석 및 μΆ”μ²œ
        recommendations = [
            "μ˜€ν›„ 7-9μ‹œμ— μ‚¬μš©λŸ‰μ΄ κ°€μž₯ λ†’μŠ΅λ‹ˆλ‹€. μ„ΈνƒκΈ°λŠ” 심야 μ‹œκ°„μ— μ‚¬μš©ν•˜μ„Έμš”.",
            "평균보닀 20% 많이 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λŒ€κΈ°μ „λ ₯을 μ°¨λ‹¨ν•˜μ„Έμš”.",
            "λƒ‰λ‚œλ°© μ„€μ • μ˜¨λ„λ₯Ό μ‘°μ ˆν•˜λ©΄ μ›” 15,000원 μ ˆμ•½ν•  수 μžˆμŠ΅λ‹ˆλ‹€."
        ]
    }

    var averageDaily: Double {
        weeklyData.map { $0.energy }.reduce(0, +) / Double(weeklyData.count)
    }

    var estimatedMonthlyCost: Double {
        averageDaily * 30 * 300
    }
}

struct UsagePatternView: View {
    @State private var analyzer = UsagePatternAnalyzer()

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                // μ£Όκ°„ 좔이
                VStack(alignment: .leading) {
                    Text("μ£Όκ°„ μ‚¬μš© 좔이")
                        .font(.headline)

                    Chart(analyzer.weeklyData) { usage in
                        BarMark(
                            x: .value("λ‚ μ§œ", usage.date, unit: .day),
                            y: .value("μ—λ„ˆμ§€", usage.energy)
                        )
                        .foregroundStyle(
                            LinearGradient(
                                colors: [.blue, .cyan],
                                startPoint: .top,
                                endPoint: .bottom
                            )
                        )

                        RuleMark(y: .value("평균", analyzer.averageDaily))
                            .foregroundStyle(.red)
                            .lineStyle(StrokeStyle(lineWidth: 2, dash: [5]))
                            .annotation(position: .top, alignment: .trailing) {
                                Text("평균")
                                    .font(.caption)
                                    .foregroundStyle(.red)
                            }
                    }
                    .frame(height: 200)
                    .chartXAxis {
                        AxisMarks { value in
                            AxisValueLabel(format: .dateTime.weekday(.narrow))
                        }
                    }
                }

                // 톡계
                HStack(spacing: 12) {
                    VStack {
                        Text("일평균")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                        Text("\(analyzer.averageDaily, specifier: "%.1f")")
                            .font(.title2)
                            .fontWeight(.bold)
                        Text("kWh")
                            .font(.caption)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(12)

                    VStack {
                        Text("μ›”μ˜ˆμƒ")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                        Text("\(Int(analyzer.estimatedMonthlyCost))")
                            .font(.title2)
                            .fontWeight(.bold)
                        Text("원")
                            .font(.caption)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.green.opacity(0.1))
                    .cornerRadius(12)
                }

                // 피크 νƒ€μž„
                VStack(alignment: .leading, spacing: 8) {
                    Label("μ΅œλŒ€ μ‚¬μš© μ‹œκ°„", systemImage: "clock.fill")
                        .font(.headline)

                    Text("μ˜€ν›„ \(analyzer.peakHour - 12)μ‹œ")
                        .font(.title3)
                        .fontWeight(.semibold)
                        .foregroundStyle(.blue)

                    Text("μ „κΈ° μš”κΈˆμ΄ μ €λ ΄ν•œ 심야 μ‹œκ°„(μ˜€ν›„ 11μ‹œ~μ˜€μ „ 9μ‹œ)에 κ³ μ „λ ₯ κΈ°κΈ°λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.gray.opacity(0.05))
                .cornerRadius(12)

                // μΆ”μ²œ 사항
                VStack(alignment: .leading, spacing: 16) {
                    Label("맞좀 μΆ”μ²œ", systemImage: "sparkles")
                        .font(.headline)

                    ForEach(Array(analyzer.recommendations.enumerated()), id: \.0) { index, tip in
                        HStack(alignment: .top, spacing: 12) {
                            Text("\(index + 1)")
                                .font(.caption)
                                .fontWeight(.bold)
                                .foregroundStyle(.white)
                                .frame(width: 24, height: 24)
                                .background(Color.blue)
                                .clipShape(Circle())

                            Text(tip)
                                .font(.subheadline)
                        }
                    }
                }
                .padding()
                .background(Color.blue.opacity(0.05))
                .cornerRadius(12)
            }
            .padding()
        }
        .navigationTitle("μ‚¬μš© νŒ¨ν„΄ 뢄석")
    }
}

πŸ’‘ HIG Guidelines

🎯 Practical Usage

πŸ“š Learn More

⚑️ Performance Tips: EnergyKit leverages HomeKit infrastructure, so it works without a separate hub. Power measurement data updates every 5 seconds, but use smooth animations for better UX. Store large history data locally with CoreData or SwiftData, and sync to iCloud as needed. Energy saving tips can be personalized using machine learning to learn usage patterns.

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation 🎬 WWDC Sessions