← Back to index

SpotsComplication

SpotsComplication
CodeWhat It DoesHow It Does It
▶ IMPORTS
import WidgetKit import SwiftUIFramework importsImports WidgetKit, SwiftUI.
▶ APP GROUP KEY (MUST MATCH WATCHENTRYSTORE)
private let appGroupID = "group.com.spots.shared" private let entriesKey = "watchEntries"`appGroupID` letProperty `appGroupID`.
▶ TIMELINE ENTRY
struct EntryEntry: TimelineEntry { let date: Date let playName: String let cuisine: String let neighborhood: String let toTryCount: Int // total "to try" entries in the list // Placeholder / empty state shown in the widget gallery static let placeholder = EntryEntry( date: Date(), playName: "Katz's", cuisine: "Delicatessen", neighborhood: "Lower East Side", toTryCount: 12 ) }`EntryEntry` structDefines the `EntryEntry` struct. Conforms to TimelineEntry.
▶ TIMELINE PROVIDER
struct EntryTimelineProvider: TimelineProvider { func placeholder(in context: Context) -> EntryEntry { .placeholder } func getSnapshot(in context: Context, completion: @escaping (EntryEntry) -> Void) { completion(topEntry() ?? .placeholder) } func getTimeline(in context: Context, completion: @escaping (Timeline<EntryEntry>) -> Void) { let entry = topEntry() ?? .placeholder // Refresh every 30 minutes — entries change infrequently let nextRefresh = Calendar.current.date(byAdding: .minute, value: 30, to: Date()) ?? Date() let timeline = Timeline(entries: [entry], policy: .after(nextRefresh)) completion(timeline) } // MARK: - Helpers /// Returns the highest-priority "to try" entry (not yet visited). /// Sorts by rating descending, then alphabetically. Falls back to any entry if all are "been". private func topEntry() -> EntryEntry? { guard let defaults = UserDefaults(suiteName: appGroupID), let data = defaults.data(forKey: entriesKey), let entries = try? JSONDecoder().decode([WatchEntry].self, from: data), !entries.isEmpty else { return nil } let toTry = entries.filter { !$0.beenThere } let pool = toTry.isEmpty ? entries : toTry // fall back to all if none left to try // Highest rating first, then A–Z let top = pool.sorted { if $0.rating != $1.rating { return $0.rating > $1.rating } return $0.playName.lowercased() < $1.playName.lowercased() }.first! return EntryEntry( date: Date(), playName: top.playName, cuisine: top.cuisine, neighborhood: top.neighborhood, toTryCount: toTry.count ) } }`EntryTimelineProvider` structDefines the `EntryTimelineProvider` struct. Conforms to TimelineProvider.
▶ COMPLICATION VIEWS
/// Circular: app icon only struct CircularComplicationView: View { let entry: EntryEntry var body: some View { ZStack { AccessoryWidgetBackground() Image("SpotsIcon") .renderingMode(.original) .resizable() .scaledToFit() .clipShape(Circle()) .padding(4) } } }Documentation commentDescribes the following declaration.
/// Rectangular: spot name + cuisine or neighborhood struct RectangularComplicationView: View { let entry: EntryEntry var body: some View { VStack(alignment: .leading, spacing: 2) { Text(entry.playName) .font(.system(size: 13, weight: .semibold)) .lineLimit(1) HStack { let detail = entry.cuisine.isEmpty ? entry.neighborhood : entry.cuisine if !detail.isEmpty { Text(detail) .font(.system(size: 12)) .foregroundColor(.secondary) .lineLimit(1) } Spacer() if entry.toTryCount > 0 { Text("\(entry.toTryCount) to try") .font(.system(size: 11)) .foregroundColor(.secondary) } } } .padding(.horizontal, 2) } }Documentation commentDescribes the following declaration.
/// Inline: single line — top spot name struct InlineComplicationView: View { let entry: EntryEntry var body: some View { let detail = entry.cuisine.isEmpty ? entry.neighborhood : entry.cuisine if detail.isEmpty { Text(entry.playName) } else { Text("\(entry.playName) · \(detail)") } } }Documentation commentDescribes the following declaration.
▶ WIDGET CONFIGURATION
struct SpotsComplication: Widget { let kind = "SpotsComplication" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: EntryTimelineProvider()) { entry in SpotsComplicationEntryView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("Spots") .description("Shows your top-priority spot to try next.") .supportedFamilies([ .accessoryCircular, .accessoryRectangular, .accessoryInline ]) } }`SpotsComplication` structDefines the `SpotsComplication` struct. Conforms to Widget.
/// Root dispatcher — picks the right view for each complication family struct SpotsComplicationEntryView: View { @Environment(\.widgetFamily) var family let entry: EntryEntry var body: some View { switch family { case .accessoryCircular: CircularComplicationView(entry: entry) case .accessoryRectangular: RectangularComplicationView(entry: entry) case .accessoryInline: InlineComplicationView(entry: entry) default: CircularComplicationView(entry: entry) } } }Documentation commentDescribes the following declaration.