I'm currently developing an application using SwiftUI
and trying to make a widget iOS 14
users can check a list of timers.
This Widget
has multiple Text(Data(),style: .timer)
to show some date data as timer. and when the rest of the value for the timer is over, I want to show it like this 00:00
.
So I implemented some way in getTimeline
function referring to this article SwiftUI iOS 14 Widget CountDown
But I don't know how can I do it the same way for multiple timers...
In the case of my code below, each timer shows the same value, because I
don't know how should I make an entries
for timeline
to handle in the case of multiple timers.
Is there any way to display what I want?
Here are the codes:
import WidgetKit
import SwiftUI
import CoreData
struct Provider: TimelineProvider {
var moc = PersistenceController.shared.managedObjectContext
init(context : NSManagedObjectContext) {
self.moc = context
}
func placeholder(in context: Context) -> SimpleEntry {
var timerEntities:[TimerEntity]?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntities = result
}
catch let error as NSError{
print("Could not fetch.(error.userInfo)")
}
return SimpleEntry(date: Date(), timerEntities: timerEntities!, duration: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
var timerEntities:[TimerEntity]?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntities = result
}
catch let error as NSError{
print("Could not fetch.(error.userInfo)")
}
let currentDate = Date()
let firstDuration = Calendar.current.date(byAdding: .second, value: 300, to: currentDate)!
let entry = SimpleEntry(date: Date(), timerEntities: timerEntities!, duration: firstDuration)
return completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var timerEntities:[TimerEntity]?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntities = result
}
catch let error as NSError{
print("Could not fetch.(error.userInfo)")
}
let currentDate = Date()
let duration = timerEntities?[0].duration ?? 0
let firstDuration = Calendar.current.date(byAdding: .second, value: Int(duration) - 1, to: currentDate)!
let secondDuration = Calendar.current.date(byAdding: .second, value: Int(duration), to: currentDate)!
let entries: [SimpleEntry] = [
SimpleEntry(date: currentDate, timerEntities: timerEntities!, duration: secondDuration),
SimpleEntry(date: firstDuration, timerEntities: timerEntities!, duration: secondDuration, isDurationZero: true)
]
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let timerEntities:[TimerEntity]
let duration:Date
var isDurationZero:Bool = false
}
struct TimerWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
return (
ForEach(entry.timerEntities){(timerEntity:TimerEntity) in
HStack{
Text(timerEntity.task!)
if !entry.isDurationZero{
Text(entry.duration, style: .timer)
.multilineTextAlignment(.center)
.font(.title)
}
else{
Text("00:00")
.font(.title)
}
}
}
)
}
}
@main
struct TimerWidget: Widget {
let kind: String = "TimerWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(context: PersistenceController.shared.managedObjectContext)) { entry in
TimerWidgetEntryView(entry: entry)
.environment(.managedObjectContext, PersistenceController.shared.managedObjectContext)
}
.supportedFamilies([.systemMedium, .systemLarge])
}
}
UPDATED:
Types of the field in TimerEntity
id: UUID
duration: Double
setDuration: Double
task: String
status: String
When users add duration
, setDurarion
also saves the same value as the duration
.
description of how timers are handled
In the Host App, when the duration
value that to be counted as a timer becomes 0, the status
is set to stoped
, and 00:00 is displayed.
And then if users tap the reset button, it returns to the value of setDuration
and displays it, so that if a timer finishes It will not be deleted from the CoreData
.
In the Widget
I tried to use isDurationZero:Bool
to detect a condition to display 00:00
instead of using status
in the host App.
timerEntities?[0] .duration ?? 0
Does this mean these timers fire repeatedly every duration seconds?
The timer runs every second.
As explained the field type in the CoreData
, the duration
type is Double
, but Casting to Int
type to correspond to (byAdding: .second) of Calendar.current.date () as below:
let firstDuration = Calendar.current.date(byAdding: .second, value: Int(duration) - 1, to: currentDate)!
let secondDuration = Calendar.current.date(byAdding: .second, value: Int(duration), to: currentDate)!
UPDATED2:
What if your app is not running but the widget is?
If the timer is not running in the host app, the timer in the widget will not work either (there are any start or stop buttons in the widget and all operations are done in the app).
If I don't need display 00:00
on each timer in Widget
the code for the Widget
is like below:
import WidgetKit
import SwiftUI
import CoreData
struct Provider: TimelineProvider {
var moc = PersistenceController.shared.managedObjectContext
init(context : NSManagedObjectContext) {
self.moc = context
}
func placeholder(in context: Context) -> SimpleEntry {
var timerEntities:[TimerEntity]?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntities = result
}
catch let error as NSError{
print("Could not fetch.(error.userInfo)")
}
return SimpleEntry(date: Date(), timerEntities: timerEntities!)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
var timerEntities:[TimerEntity]?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntities = result
}
catch let error as NSError{
print("Could not fetch.(error.userInfo)")
}
let entry = SimpleEntry(date: Date(), timerEntities: timerEntities!)
return completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var timerEntities:[TimerEntity]?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntities = result
}
catch let error as NSError{
print("Could not fetch.(error.userInfo)")
}
let entries: [SimpleEntry] = [
SimpleEntry(date: Date(), timerEntities: timerEntities!)
]
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let timerEntities:[TimerEntity]
}
struct TimerWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
return (
VStack(spacing:5){
ForEach(0..<3){index in
HStack{
Text(entry.timerEntities[index].task ?? "")
.font(.title)
Text(entry.timerEntities[index].status ?? "")
.font(.footnote)
Spacer()
if entry.timerEntities[index].status ?? "" == "running"{
Text(durationToDate(duration: entry.timerEntities[index].duration), style: .timer)
.multilineTextAlignment(.center)
.font(.title)
}else{
Text(displayTimer(duration: entry.timerEntities[index].duration))
.font(.title)
}
}
}
}
)
}
}
@main
struct TimerWidget: Widget {
let kind: String = "TimerWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(context: PersistenceController.shared.managedObjectContext)) { entry in
TimerWidgetEntryView(entry: entry)
.environment(.managedObjectContext, PersistenceController.shared.managedObjectContext)
}
.supportedFamilies([.systemMedium, .systemLarge])
}
}
//MARK: - funcs for Widget
func durationToDate(duration:Double) -> Date{
let dateDuration = Calendar.current.date(byAdding: .second, value: Int(duration), to: Date())!
return dateDuration
}
func displayTimer(duration:Double) -> String {
let hr = Int(duration) / 3600
let min = Int(duration) % 3600 / 60
let sec = Int(duration) % 3600 % 60
if duration > 3599{
return String(format: "%02d:%02d:%02d", hr, min, sec)
}else{
return String(format: "%02d:%02d", min, sec)
}
}
But in this case, after each timer displays 0:00
it starts to count up based on Text(Data(),style: .timer)
specification.(
I want to keep the display as 0:00
when the timer expires)
But how can you know that the timer finished if you only sto