Update Widget Every Minute

Is this not possible? I have a simple text view that shows the time, but it doesn't stay in sync with the time. I've tried to use a timer like I do inside the app, but that didn't work. I tried TimelineView with it set to every minute, and that didn't work. I tried to use the dynamic date using Text(date, style: ) and that didn't work. I looked it up and apparently it's a limitation to WidgetKit, however, I don't understand how the Apple Clock widget can have a moving second hand that updates properly.

Do I just need to add a bunch of entries in the TimelineProvider? Right now, it's just the default that gets loaded when you create a Widget extension.

    func timeline(for configuration: SingleClockIntent, in context: Context) async -> Timeline<SingleClockEntry> {
        var entries: [SingleClockEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SingleClockEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        return Timeline(entries: entries, policy: .atEnd)
    }

EDIT: This code below seems to be working. Can someone confirm if it will continue to work, or will it eventually stop updating?

func timeline(for configuration: SingleClockIntent, in context: Context) async -> Timeline<SingleClockEntry> {
        var entries: [SingleClockEntry] = []

        let calendar = Calendar.current
        let current  = Date()
        
        // Get the next minute
        let secondEntryDate  = calendar.nextDate(after: current, matching: DateComponents(second: 0), matchingPolicy: .strict, direction: .forward)!
        
        entries.append(SingleClockEntry(date: current, configuration: configuration)) // Adds current time
        entries.append(SingleClockEntry(date: secondEntryDate, configuration: configuration)) // Adds next minute after current time
        
        // Add an entry every min for the next 30 minutes
        for offset in 0..<30 {
            let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: secondEntryDate)!
            let entry = SingleClockEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        return Timeline(entries: entries, policy: .atEnd)
    }

It stopped updating as expected, but then decided to work again after about 15 minutes. I have the following code in the timeline that I thought would work, but didn't.

let refresh = calendar.date(byAdding: .second, value: 1, to: entries.last!.date)!
        return Timeline(entries: entries, policy: .after(refresh))

First, you are in a losing battle using the things Apple does to gage what it is possible for you to do. It is not realistically possible to duplicate the clock widgets within the limits that are provided for 3rd party widgets. Apple has granted their own widgets special permissions.

It is possible to add a timeline entry for each minute, but you have to hit a balance between overrunning memory limits (30MB as far as I can tell) and limiting the number of refreshes.

Timeline entries will show at exactly the time you specify, but the same is not true for refreshes. You are able to tell the system when to refresh, but they don't provide any sort of guarantee as to when the refresh will actually happen. In your example created 30 minutes of entries and then said to update at the end of those 30 minutes. It didn't actually do the refresh for another 15 minutes. You are going to run into trouble doing a refresh more than once per hour, but I am not sure that is even a sure thing. Regardless of how often you do the refresh, if you want timing precision, you need specify the refresh at a particular time and then have the timeline entries to go past the refresh time.

As an example (that may or may not be good numbers), you generate 2 hours worth of timeline entries and then set the refresh to happen every hour.

You're allowed 72 refreshes in a 24-hour period, so that's once every 20 minutes.

One of my own apps displays timers on the widgets, and they generally keep in sync when I use one timeline entry with a date of now, and set it to refresh after now + 20 minutes, i.e.

let date = Date()
let refreshDate = Calendar.autoupdatingCurrent.date(byAdding: .minute, value: 20, to: date)!

...

return Timeline(entries: entries, policy: .after(refreshDate))

I say they "generally" keep in sync. There can be occasions where they'll be a minute or two out - as @jcgoforth explained - so I work around that.

(For info, if the timer is going to hit zero before the 20 minutes, then the refreshDate is set to the actual date of the end of the timer, so the refresh happens then rather than up to 20 minutes later.)

The best thing you can do is use the preview timeline in Xcode. Add something like this to your widget view, and open the preview canvas in Xcode. You can then scroll through the entries and see how they'll look:

#Preview(as: .systemMedium) {
  WidgetExtension()
} timeline: {
  for offset in 0..<30 {
    let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: secondEntryDate)!
    let entry = SingleClockEntry(date: entryDate, configuration: configuration)
    entries.append(entry)
}
Update Widget Every Minute
 
 
Q