This question already has an answer here:
SwiftUI iOS 14 Widget CountDown
(1 answer)
Closed 2 years ago.
So I am new to WidgetKit and SwiftUI, but is there an event or a way to detect when the countdown reaches 00:00 for Text()?
let components = DateComponents(minute: 15)
let futureDate = Calendar.current.date(byAdding: components, to: Date())!
Text(futureDate, style: .timer)
It appears that it is either poorly documented -to me at least- or there is still nothing like that..?
It's not possible, when it reaches 0 it will start counting up.
You have to use the timeline to refresh that widget at the end date of your countdown, using a normal Text that just shows 0:00:00
an example:
var entries: [SingleEntry] = []
// First entry at Date() which is now... with the countdown endDate at 60 seconds in the future
// which you'll use in the Text(date, style)
entries.append(
SingleEntry(
date: Date(),
configuration: configuration,
endDate: Date().addingTimeInterval(60)
)
)
// Second entry which will be scheduled at the Date when you want to stop the timer
// in the TimelineEntry now you can check if endDate is nil and use a normale text
// to say that the countdown is over
entries.append(
SingleEntry(
date: Date().addingTimeInterval(60),
configuration: configuration,
endDate: nil
)
)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
Related
For testing purposes, I'm trying to refresh a widget every 30 sec with the following code.
// PWContent is the TimelineEntry object.
func getTimeline(in context: Context, completion: #escaping (Timeline<PWContent>) -> ()) {
NSLog("PWTimelineProvider.getTimeline(in)")
let currentDate: Date = Date.now
let calendar: Calendar = Calendar.current
// Display a random element from the array
let entry: PWContent = PWContent(date: currentDate, planet: PWCustomData.sPlanets.randomElement()!)
NSLog("Current time = " + String(describing: currentDate))
// Set refresh date to 30sec in the future.
let refreshDate = calendar.date(byAdding: .second, value: 30, to: currentDate)!
NSLog("Refresh time = " + String(describing: refreshDate))
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
completion(timeline)
}
But I get the following logs
PWTimelineProvider.getTimeline(in)
Current time = 2023-01-08 07:53:31 +0000
Refresh time = 2023-01-08 07:54:01 +0000
PWTimelineProvider.getTimeline(in)
Current time = 2023-01-08 07:58:31 +0000
Refresh time = 2023-01-08 07:59:01 +0000
PWTimelineProvider.getTimeline(in)
Current time = 2023-01-08 08:03:31 +0000
Refresh time = 2023-01-08 08:04:01 +0000
As shown in the above logs, the widget refreshes every 5 mins (The difference b/w two 'Current Time'). When checking the Widget in home screen, it doesn't update every 30 sec.
My understanding (which is not what's observed): Widget first updates itself at 7:53:31. When the time is 7:54:01, getTimeline function is invoked (since the refresh policy was set to 7:54:01 i.e. 30 sec later than the previous time) by iOS to request another timeline, which again provides only one entry at the current time (= 7:54:01) and the new refresh policy is set to 7:54:31, which is when getTimeline is invoked again to get the next timeline.
Reference: Provide Timeline Entries section in this wiki.
According to documentation,
WidgetKit imposes a minimum amount of time before it reloads a widget. Your timeline provider should create timeline entries that are at least about 5 minutes apart.
And that's why, despite setting up a refresh period of 30sec, it still refreshes after 5mins.
But, in the following getTimeline(),
// ISWContent is the TimelineEntry object.
func getTimeline(in context: Context, completion: #escaping (Timeline<ISWContent>) -> ()) {
NSLog(ISW_TAG + "ISWTimelineProvider.getTimeline(in)")
let increment: Int = 15
var entries: [ISWContent] = []
let currentDate = Date.now
// Generate a timeline consisting of 4 entries 15 sec apart,
// starting from the current date.
NSLog(ISW_TAG + "Current time = " + String(describing: currentDate))
for index in 0...3 {
let entryDate = Calendar.current.date(byAdding: .second, value: (index * increment), to: currentDate)!
NSLog(ISW_TAG + "incremental dates[%d] = " + String(describing: entryDate), index)
let entry = ISWContent(date: entryDate, state: ISWCustomData.sIndianStates.randomElement()!)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
Widget is refreshed every 15secs and once the last timeline entry has expired, getTimeline is invoked immediately to provide the next timeline. The 5mins restriction doesn't apply here.
Anyway, according to documentation, whatever timeline we return, is only a request and may not be granted if the user rarely views the home screen containing the widget or the app.
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
}
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
This question already has an answer here:
Setting the TimelineProvider refresh interval for Widget
(1 answer)
Closed 2 years ago.
When I run the code snippet below, WidgetKit is supposed to request a new timeline at the end of each minute according to the Apple's documentation.
Code Block
let currentDate = Date()
let futureDate = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate)!
let timeline = Timeline(entries: [
Entry(date: currentDate, number: Int.random(in: 0...10))], policy: .after(futureDate))
completion(timeline)
But it just works once and nothing happens afterward as if it has been set to .never. I am trying it on iOS 14 Beta 2.
That's because your updates are too aggressive.
Change it to 15 minutes and it will work
This question already has answers here:
Checking response Time of API in iOS using Swift 3?
(4 answers)
Closed 3 years ago.
I'm trying to find a way to tell how long its taken a function to execute in seconds. Currently I'm doing it this way:
let startDate = Date()
let endDate = Date()
let calendar = Calendar.current
let dateComponents = calendar.compare(startDate, to: endDate, toGranularity: .second)
let seconds = dateComponents.rawValue
print("Seconds: \(seconds)")
but every time I print out the seconds it always reads -1. I've looked into this question: elapsed time but I need the output to be in seconds. Any suggestions?
Try this:
let start = Date.timeIntervalSinceReferenceDate
// do stuff
let end = Date.timeIntervalSinceReferenceDate
let secondsElapsed = end - start
secondsElapsed will be a Double, but it will be in seconds. You can round it or truncate it if you want an Int.
Hope this helps!
I am having a filter on a tableView, that has a 'From' and 'To' Months selection.
Month selection, I mean, when I select Month, the datePicker should show Month Year only. And the list should contain last 12 months from today.
I looked at the default UIDatePicker but realized that it is not possible to get only Month-Year. (Reference answer from SO)
Further looking, I found this link that has a custom month-year picker but it shows future 15 years. And, upon selection of any row, nothing happens.
Can someone help me customize it to show last 12 months from today and fetch the result upon selection?
This is a simple example to get the last 12 months
var last12Months = [Date]()
let firstDayComponent = DateComponents(day: 1)
Calendar.current.enumerateDates(startingAfter: Date(),
matching: firstDayComponent,
matchingPolicy: .nextTime,
direction: .backward,
using: { (date, idx, stop) in
if let date = date {last12Months.append(date) }
if last12Months.count == 12 { stop = true }
})
print(last12Months)
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
print(last12Months.map({formatter.string(from: $0)}))