Section 7/141 menit

7. Widget Families dan Adaptive Layout

7. Widget Families dan Adaptive Layout

Semua Ukuran yang Tersedia

swift
struct AdaptiveWidgetView: View {
    @Environment(\.widgetFamily) var family  // Deteksi ukuran aktif
    let entry: HabitEntry

    var body: some View {
        switch family {
        case .systemSmall:
            SmallHabitView(entry: entry)
        case .systemMedium:
            MediumHabitView(entry: entry)
        case .systemLarge:
            LargeHabitView(entry: entry)
        case .accessoryCircular:
            // Lock Screen / Apple Watch circular
            CircularHabitView(entry: entry)
        case .accessoryRectangular:
            // Lock Screen rectangular
            RectangularHabitView(entry: entry)
        case .accessoryInline:
            // Lock Screen inline (satu baris teks)
            Text("\(entry.habitName): \(entry.isCompleted ? "✓" : "○")")
        @unknown default:
            SmallHabitView(entry: entry)
        }
    }
}

Lock Screen Widgets (iOS 16+)

swift
// Lock Screen widget harus menggunakan .containerBackground yang tepat
struct CircularHabitView: View {
    let entry: HabitEntry

    var body: some View {
        ZStack {
            AccessoryWidgetBackground()
            VStack(spacing: 0) {
                Image(systemName: entry.isCompleted ? "checkmark" : "circle")
                    .font(.title3)
                Text("\(entry.streak)")
                    .font(.caption2.bold())
            }
        }
        .containerBackground(.clear, for: .widget)
    }
}

// Daftarkan ke supported families
struct HabitWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "HabitWidget", provider: HabitProvider()) { entry in
            AdaptiveWidgetView(entry: entry)
        }
        .supportedFamilies([
            .systemSmall,
            .systemMedium,
            .accessoryCircular,       // Lock Screen
            .accessoryRectangular,    // Lock Screen
            .accessoryInline          // Lock Screen (teks saja)
        ])
    }
}

Interactive Widgets (iOS 17+)

Widget kini bisa memiliki tombol dan toggle yang langsung menjalankan App Intent:

swift
import AppIntents

// Definisikan intent yang akan dijalankan saat tombol ditekan
struct CompleteHabitIntent: AppIntent {
    static var title: LocalizedStringResource = "Tandai Habit Selesai"
    static var description = IntentDescription("Menandai habit hari ini sebagai selesai")

    @Parameter(title: "Habit ID")
    var habitID: String

    func perform() async throws -> some IntentResult {
        // Kode ini berjalan di proses app extension
        let sharedDefaults = UserDefaults(suiteName: "group.com.perusahaan.namaapp")
        sharedDefaults?.set(true, forKey: "habitCompleted_\(habitID)")
        // Trigger reload widget
        return .result()
    }
}

// Widget view dengan Button interaktif
struct InteractiveHabitView: View {
    let entry: HabitEntry

    var body: some View {
        VStack(spacing: 12) {
            Text(entry.habitName)
                .font(.headline)

            // Button ini bekerja langsung di widget — tidak buka app
            Button(intent: CompleteHabitIntent(habitID: entry.habitID)) {
                Label(
                    entry.isCompleted ? "Selesai ✓" : "Tandai Selesai",
                    systemImage: entry.isCompleted ? "checkmark.circle.fill" : "circle"
                )
            }
            .buttonStyle(.borderedProminent)
            .disabled(entry.isCompleted)
        }
        .containerBackground(.fill.tertiary, for: .widget)
    }
}