Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Widget: getTimeline with completion handler

I have a iOS Widget that I am trying to update every 5 or 15 minutes.

I am new to widgets and do not understand how to loop the timeline with an async call.

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
    let price: Double
}

func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        
        
        networkManager.fetchData { price in
            
            var entries: [SimpleEntry] = []
            let currentDate = Date()
            
            for hourOffset in 0 ..< 5 {
                
                let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
                let entry = SimpleEntry(date: entryDate, configuration: configuration, price: price)
                entries.append(entry)
                
            }
            
            let timeline = Timeline(entries: entries, policy: .atEnd)
            completion(timeline)
        }
       
    }

I have a completion handler that passes in the price from the async api call.

networkManager.fetchData { price in 
}
like image 467
CoderChick Avatar asked Sep 25 '26 23:09

CoderChick


1 Answers

It looks like your networkManager.fetchData is providing only one price value at a time.
In such cases, when only one entry is provided at a time then the following should be enough:

func getTimeline(for configuration: ConfigurationIntent, 
                 in context: Context, 
                 completion: @escaping (Timeline<Entry>) -> ()) {
  networkManager.fetchData { (price) in
    let currentDate = Date()

    //create the entry for the given price
    let entry = SimpleEntry(date: currentDate, 
                            configuration: configuration, 
                            price: price)

    /*
     If you can predict price values then you need the loop for multiple entries.
     However in your case, it seems this one entry is sufficient
     */
    let entries = [entry]

    //next reload date; 15mins in this case
    let reloadDate = Calendar.current.date(byAdding: .minute, 
                                           value: 15,
                                           to: currentDate)!

    //your timeline with one entry that will refresh after given date
    let timeline = Timeline(entries: entries, policy: .after(reloadDate))
    completion(timeline)
  }
}

By specifying after(_ date:) as the TimelineReloadPolicy, iOS will refresh the widget after the given date.

The timeline’s refresh policy specifies the earliest date for WidgetKit to request a new timeline from the provider. The default refresh policy, .atEnd, tells WidgetKit to request a new timeline after the last date in the array of timeline entries you provide. However, you can use .afterDate to indicate a different date either earlier or later than the default date. Specify an earlier date if you know there’s a point in time before the end of your timeline entries that may alter the timeline.

Ref: Apple Documentation on Timeline


This solution was to address the core issue, so finally I would like to add a disclaimer that reloading every 15mins might be overkill. Hence tweak your logic to specify the TimelineReloadPolicy intelligently. If you can predict values & create a timeline of multiple entries with fewer network calls then great! If not then... well... best of luck :)

Important

Plan ahead if your widget makes requests to a server when it reloads, and uses afterDate() with a specific date in timeline entries. WidgetKit tries to respect the date you specify, which may cause a significant increase in server load when multiple devices reload your widget at around the same time.

Ref: Keeping a Widget Up To Date

More Read:

  • Apple Documentation on TimelineProvider
like image 147
staticVoidMan Avatar answered Sep 27 '26 17:09

staticVoidMan