Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating time text label each minute in WidgetKit

Is it possible to create text label in Widget that will show current time and will be updating in real-time?

Trying to create clock widget, but widget is updating only 1 time each 5 minutes.

  • Creation of timeline not helped
  • "Keeping a Widget Up To Date" not working with current time, only timers, etc.
like image 859
Maks Avatar asked Sep 24 '20 20:09

Maks


People also ask

How do I change the timeline of a widgetkit widget?

Your app can tell WidgetKit to request a new timeline when something affects a widget’s current timeline. In the game widget example above, if the app receives a push notification indicating a teammate has given the character a healing potion, the app can tell WidgetKit to reload the timeline and update the widget’s content.

Can I display time-based information that widgetkit updates?

Even though your widget doesn’t run continually, it can display time-based information that WidgetKit updates live. For example, it might display a countdown timer that continues to count down even if your widget extension isn’t running. For more information, see Displaying Dynamic Dates in Widgets.

When should I request a timeline reload from widgetkit?

For cases such as system appearance changes or system locale changes, don’t request a timeline reload from your app. The system updates your widgets automatically. Although your widget timeline provider drives your reload schedule, WidgetKit sometimes reloads your widget to help keep its content fresh.

How do I track when to update my widget?

WidgetKit gets a timeline from your provider, and uses it to track when to update your widget. A timeline is an array of TimelineEntry objects. Each entry in the timeline has a date and time, and additional information the widget needs to display its view.


2 Answers

A possible solution is to use the time date style:

/// A style displaying only the time component for a date.
///
///     Text(event.startDate, style: .time)
///
/// Example output:
///     11:23PM
public static let time: Text.DateStyle

  1. You need a simple Entry with a Date property:
struct SimpleEntry: TimelineEntry {
    let date: Date
}
  1. Create an Entry every minute until the next midnight:
struct SimpleProvider: TimelineProvider {
    ...

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        var entries = [SimpleEntry]()
        let currentDate = Date()
        let midnight = Calendar.current.startOfDay(for: currentDate)
        let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!

        for offset in 0 ..< 60 * 24 {
            let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: midnight)!
            entries.append(SimpleEntry(date: entryDate))
        }

        let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
        completion(timeline)
    }
}
  1. Display the date using the time style:
struct SimpleWidgetEntryView: View {
    var entry: SimpleProvider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}

If you want to customise the date format you can use your own DateFormatter:

struct SimpleWidgetEntryView: View {
    var entry: SimpleProvider.Entry
    
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "HH:mm"
        return formatter
    }()

    var body: some View {
        Text("\(entry.date, formatter: Self.dateFormatter)")
    }
}

Here is a GitHub repository with different Widget examples including the Clock Widget.

like image 63
pawello2222 Avatar answered Oct 19 '22 14:10

pawello2222


Thanks very much for @pawello222's answer, but the problem with this solution is that too many stuff (24 * 60) are saved in the Timeline.

Once the widget's view contains a lot of elements (like 5 or more Text(...) in my case), then the widget will completely stop rendering on the ios real device, and xcode will report this:

2020-12-03 11:22:46.094442+0800 PixelClockWidgetExtension[660:53387] 
[default] -[EXSwiftUI_Subsystem beginUsing:withBundle:] unexpectedly called multiple times.

I think one possible solution is to divide the daily time into several small pieces and save them in the Timeline multiple times.

My Environment:

  • ios 14.2
  • Xcode 12.2
  • iPhone 12 pro
like image 4
D.Sai Avatar answered Oct 19 '22 14:10

D.Sai