Section 5/141 menit

5. Sintaks Dasar: Widget Pertama

5. Sintaks Dasar: Widget Pertama

Tiga Komponen Utama

Setiap widget membutuhkan tiga komponen: Entry (data), Provider (sumber data + timeline), View (tampilan).

swift
import WidgetKit
import SwiftUI

// 1. Entry — data model untuk satu snapshot widget
struct HabitEntry: TimelineEntry {
    let date: Date           // WAJIB — kapan entry ini valid
    let habitName: String
    let isCompleted: Bool
    let streak: Int
}

// 2. Provider — menghasilkan entry untuk snapshot dan timeline
struct HabitProvider: TimelineProvider {
    // Dipanggil untuk placeholder saat widget pertama kali muncul
    func placeholder(in context: Context) -> HabitEntry {
        HabitEntry(date: Date(), habitName: "Olahraga", isCompleted: false, streak: 7)
    }

    // Dipanggil untuk preview di widget gallery
    func getSnapshot(in context: Context, completion: @escaping (HabitEntry) -> Void) {
        let entry = HabitEntry(date: Date(), habitName: "Olahraga", isCompleted: true, streak: 14)
        completion(entry)
    }

    // Dipanggil untuk timeline aktual
    func getTimeline(in context: Context, completion: @escaping (Timeline<HabitEntry>) -> Void) {
        let sharedDefaults = UserDefaults(suiteName: "group.com.perusahaan.namaapp")
        let habitName = sharedDefaults?.string(forKey: "currentHabit") ?? "Olahraga"
        let isCompleted = sharedDefaults?.bool(forKey: "habitCompletedToday") ?? false
        let streak = sharedDefaults?.integer(forKey: "streak") ?? 0

        let entry = HabitEntry(
            date: Date(),
            habitName: habitName,
            isCompleted: isCompleted,
            streak: streak
        )

        // Refresh di tengah malam untuk reset status harian
        let midnight = Calendar.current.startOfDay(for: Date().addingTimeInterval(86400))
        let timeline = Timeline(entries: [entry], policy: .after(midnight))
        completion(timeline)
    }
}

// 3. View — SwiftUI view yang dirender oleh sistem
struct HabitWidgetView: View {
    let entry: HabitEntry

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Image(systemName: entry.isCompleted ? "checkmark.circle.fill" : "circle")
                    .foregroundStyle(entry.isCompleted ? .green : .secondary)
                Text(entry.habitName)
                    .font(.headline)
            }
            Text("🔥 \(entry.streak) hari streak")
                .font(.caption)
                .foregroundStyle(.orange)
        }
        .padding()
        .containerBackground(.fill.tertiary, for: .widget)
    }
}

// 4. Widget definition
struct HabitWidget: Widget {
    let kind: String = "HabitWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: HabitProvider()) { entry in
            HabitWidgetView(entry: entry)
        }
        .configurationDisplayName("Habit Tracker")
        .description("Pantau habit harianmu langsung dari Home Screen.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

WidgetBundle untuk Multiple Widgets

swift
// MyAppWidgetBundle.swift — entry point untuk semua widget dalam satu extension
@main
struct MyAppWidgetBundle: WidgetBundle {
    var body: some Widget {
        HabitWidget()
        WeatherWidget()
        QuoteOfTheDayWidget()
    }
}