I am creating an app that contains a clock widget. I want to keep the widget updating preferable every second. I've had some luck creating a long timeline - but i'm still running into the throttling limition imposed by Apple
I've experimented with a fixed animation for the second hand "faking" the seconds. But the widget keeps getting "stuck" this is my c
Any tips or tricks to keep my widget running and "updating" every second?
I’ve seen multiple apps in the App Store do this. So it must be possible.
struct Provider: AppIntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), configuration: ConfigurationAppIntent()) } func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { SimpleEntry(date: Date(), configuration: configuration) } func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<SimpleEntry> { let currentDate = Date() var entries: [SimpleEntry] = [] // Create entries for the next 30 minutes, one every second let numberOfEntries = 30 * 60 // 30 minutes * 60 seconds for offset in 0..<numberOfEntries { if let entryDate = Calendar.current.date(byAdding: .second, value: offset, to: currentDate) { let entry = SimpleEntry(date: entryDate, configuration: configuration) entries.append(entry) } } // Request new timeline 5 minutes before this one ends let refreshDate = Calendar.current.date(byAdding: .minute, value: 25, to: currentDate)! print("Created timeline at: \(currentDate), next refresh at: \(refreshDate)") return Timeline(entries: entries, policy: .after(refreshDate)) } } struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationAppIntent var selectedWatchFace: WatchFace? { return configuration.selectedWatchFace?.watchFace } init(date: Date, configuration: ConfigurationAppIntent) { self.date = date self.configuration = configuration print("Created entry for: \(date)") } } struct WatchWidgetEntryView: View { var entry: Provider.Entry u/Environment(\.widgetFamily) var widgetFamily var body: some View { if let watchFace = entry.selectedWatchFace { WatchPreview( background: watchFace.background, hourHand: watchFace.hourHand, minuteHand: watchFace.minuteHand, secondHand: watchFace.secondHand, currentTime: entry.date ) .privacySensitive(false) } else { Text("Select a watch face") .font(.caption) } } } struct WatchWidget: Widget { let kind: String = "WatchWidget" var body: some WidgetConfiguration { AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in WatchWidgetEntryView(entry: entry) .frame(maxWidth: .infinity, maxHeight: .infinity) .containerBackground(for: .widget) { Color.clear } } .configurationDisplayName("Watch Face") .description("Display your favorite watch face") .supportedFamilies([.systemSmall]) .contentMarginsDisabled() } } // Modified version of WatchPreview for the widget struct WatchPreview: View { let background: UIImage? let hourHand: UIImage? let minuteHand: UIImage? let secondHand: UIImage? let currentTime: Date private let calendar = Calendar.current u/State private var secondRotation = 0.0 private var hourRotation: Double { let hour = Double(calendar.component(.hour, from: currentTime) % 12) let minute = Double(calendar.component(.minute, from: currentTime)) return (hour * 30) + (minute * 0.5) } private var minuteRotation: Double { Double(calendar.component(.minute, from: currentTime)) * 6 } var body: some View { GeometryReader { geometry in let size = min(geometry.size.width, geometry.size.height) ZStack { if let background = background { Image(uiImage: background) .resizable() .aspectRatio(contentMode: .fill) .frame(width: size, height: size) .clipped() } if let hourHand = hourHand { Image(uiImage: hourHand) .resizable() .scaledToFit() .rotationEffect(.degrees(hourRotation)) } if let minuteHand = minuteHand { Image(uiImage: minuteHand) .resizable() .scaledToFit() .rotationEffect(.degrees(minuteRotation)) } if let secondHand = secondHand { Image(uiImage: secondHand) .resizable() .scaledToFit() .rotationEffect(.degrees(secondRotation)) } } .frame(width: size, height: size) .position(x: geometry.size.width / 2, y: geometry.size.height / 2) .onAppear { // Set initial rotation based on current seconds let second = Double(calendar.component(.second, from: currentTime)) secondRotation = second * 6 // Start continuous rotation animation withAnimation(.linear(duration: 60).repeatForever(autoreverses: false)) { secondRotation += 360 } } } } } Using timeline updates. Animation etc. Nothing seems to be reliable.