Widget ios 14 not updating daily - ios14

I have a simple widget that needs to be updated daily, however many of the users have complained that their widget doesn't update daily.
What am doing wrong here?
func getTimeline(in context: Context, completion: #escaping (Timeline<TodayInfoEntry>) -> Void) {
let now = Date() + 2
widgtBrain.date = now
let nextUpdateDate = Calendar.autoupdatingCurrent.date(byAdding: .day, value: 1, to: Calendar.autoupdatingCurrent.startOfDay(for: now))!
let todayInfo = widgtBrain.widgetInfoDicGenerator(forActualWidget: true, forPlaceholder: false, forSnapshot: false)
let entry = TodayInfoEntry(
date: now,
info: todayInfo
)
let timeline = Timeline(
entries:[entry],
policy: .after(nextUpdateDate)
)
completion(timeline)
}
the widgetBrain generates a dictionary with new data based on the given date "now"

Related

How can I keep the existing entry on WidgetKit refresh?

I'm using HealthKit data in my widget. If the phone is locked, it's not possible to get HealthKit data, only if the phone is unlocked. However, my widget timeline's will try to update even if the phone is locked.
Is it possible to return an empty completion somehow, so it will keep the current widget data untouched?
This is my code:
struct Provider: IntentTimelineProvider {
private let healthKitService = HealthKitService()
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let currentDate = Date()
let refreshDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!
healthKitService.getHeartRate() { data, error in
//Create an empty entry
var entry = SimpleEntry(date: currentDate, configuration: ConfigurationIntent(), data: nil)
//If no errors, set data
if(error == nil) {
entry.data = data
} else {
print(error) //this runs when a locked phone does the widget update
}
//Return response
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
completion(timeline)
}
}
}
What I can do is to store the entry data in UserDefaults and load that up in the error route? I'm not sure if thats a good solution though.
The main issue is that you don't have a state in the getTimeline function. This is a similar problem as in How to refresh multiple timers in widget iOS14? - you need some way to store information outside getTimeline.
As you've already mentioned, a possible solution is storing the last entry in the UserDefatuls.
However, you can also try creating your own EntryCache:
class EntryCache {
var previousEntry: SimpleEntry?
}
struct SimpleEntry: TimelineEntry {
let date: Date
var previousDate: Date?
}
struct IntentProvider: IntentTimelineProvider {
private let entryCache = EntryCache()
// ...
// in this example I'm testing if the `previousDate` is loaded correctly from the cache
func getTimeline(for configuration: TestIntentIntent, in context: Context, completion: #escaping (Timeline<SimpleEntry>) -> Void) {
let currentDate = Date()
let entry = SimpleEntry(date: currentDate, previousDate: entryCache.previousEntry?.date)
let refreshDate = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate)!
let refreshEntry = SimpleEntry(date: refreshDate, previousDate: entryCache.previousEntry?.date)
let timeline = Timeline(entries: [entry, refreshEntry], policy: .atEnd)
// store the `entry` in the `entryCache`
entryCache.previousEntry = entry
completion(timeline)
}
}
Note
I didn't find any information as to when the TimelineProvider may be re-created. In my tests the Widget was using the same Provider for every refresh but it's safer to assume that the Provider might be re-initialised at one some point in the future. Then, theoretically, for one refresh cycle the previousEntry will be nil.

WidgetKit won't share data between iOS and widget

to make it short, my iOS app download some data from a server and put it into an array.
I wanna share the array count with the widget using the AppGroups.
I save the array count number to UserDefaults like this:
if let userDefaults = UserDefaults(suiteName: "group.com.etc") {
// I save just a simple Int
userDefaults.set(loaded.count, forKey: userDefaultsKey)
}
Then on the widget side I have this class to retrieve the data:
class MyDataProvider {
static func getCountFromUserDefaults()-> Int {
if let userDefaults = UserDefaults(suiteName: "group.com.etc") {
let myFlag = userDefaults.integer(forKey: userDefaultsKey)
print("myFlag is \(myFlag)")
return myFlag
}
print("my flag is 0")
return 0
}
}
Last, my getTimeLine func is this
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// 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: .second, value: hourOffset * 30, to: currentDate)!
let entry = SimpleEntry(date: entryDate, myString: "\(MyDataProvider.getCountFromUserDefaults())")
print("my entry is \(entry)")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
The issue is that the number is always 0. On iOS side I'm sure the number is saved correctly but the widget get always 0 even when the iOS app is opened.
Do i mistake something?
Solved: I forgot to add the AppGroup to the Widget Extension too.

How to change view on midnight in WidgetKit, SwiftUI?

I have a code:
struct ContentView: View {
let entry: LessonWidgetEntry
private static let url: URL = URL(string: "widgetUrl")!
var body: some View {
VStack {
switch entry.state {
case .none:
ProgramNotStartedView()
case .currentLesson(let lesson):
LessonView(lesson: lesson, imageName: entry.program?.imageName)
case .lessonCompleted(let lesson):
LessonCompletedView(lesson: lesson)
case .programCompleted:
ProgramCompletedView()
}
}.widgetURL(ContentView.url)
}
}
At midnight LessonCompletedView should change to LessonView, but I am not sure how to do that.
Any ideas on how to change views on midnight from the widget?
Assuming you have an Entry (in your app you have entry.state... but for this example I used a simplified version):
struct SimpleEntry: TimelineEntry {
let date: Date
let lesson: Lesson
}
Setup your TimelineProvider to refresh timeline after the next midnight:
struct SimpleProvider: TimelineProvider {
...
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> Void) {
let currentDate = Date()
let midnight = Calendar.current.startOfDay(for: currentDate)
let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!
let entries = [
SimpleEntry(date: currentDate, lesson: Lesson()) // pass the lesson here
]
let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
completion(timeline)
}
}
In the TimelineProvider you may pass any lesson you want (depending on the day or the previous lesson - it's up to you). You may also pass a variable to an Entry indicating whether the lesson is completed.
By setting the .after(nextMidnight) policy you indicate when do you want your Timeline (and therefore you Widget View) to be reloaded.

Widget not getting updated even when UserDefaults are synchronized

I am using XCode 12 beta 2 (iOS 14 Sim) to pass data from my app to the widget using AppContainer.
I am using the below code to save data (here String) to app container.
let userDefaults = UserDefaults(suiteName: "group.abc.WidgetDemo")
userDefaults?.setValue(status, forKey: "widget")
userDefaults?.synchronize()
And in the Widget.swift file
struct Provider: TimelineProvider {
#AppStorage("widget", store: UserDefaults(suiteName: "group.abc.WidgetDemo"))
var status: String = String()
public func snapshot(with context: Context, completion: #escaping (MyEntry) -> ()) {
let entry = MyEntry(status: status, date: Date())
completion(entry)
}
public func timeline(with context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let entryDate = Calendar.current.date(byAdding: .second, value: 10, to: Date())!
let entry = MyEntry(status: status, date: entryDate)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Please note: Timeline entry is 10 seconds post current date.
Even after giving a 10 seconds delay, I am unable to see the updated information in the widget.
Apparently, after reading the documentation, I happen to make it work by using the below
WidgetCenter.shared.reloadTimelines(ofKind: "WidgetDemo")
But if sometimes, the above doesn't work I try to reload all the timelines.
WidgetCenter.shared.reloadAllTimelines()
Please note: the reload Timelines code is written in the source file from where we are transmitting the data.

Adding a google calendar event in swift

I am trying to create a Google Calendar event using the API in Swift. I am kind of lost at the moment in how to go about that. More specifically creating a GTLRCalendar_Event object to pass through GTLRCalendarQuery_EventsInsert.query(). Any way to go about this?
I've written the following code
var newEvent: GTLRCalendar_Event = GTLRCalendar_Event()
newEvent.summary = name
//set GTLRDateTimes
var startTime: GTLRDateTime = GTLRDateTime(date:startTimeObject!, offsetMinutes: offsetMinutes)
var endTime: GTLRDateTime = GTLRDateTime(date:endTimeObject!, offsetMinutes: offsetMinutes)
newEvent.reminders?.useDefault = 0
newEvent.start?.dateTime = startTime
newEvent.end?.dateTime = endTime
let service: GTLRCalendarService = GTLRCalendarService()
let query:GTLRCalendarQuery_EventsInsert = GTLRCalendarQuery_EventsInsert.query(withObject: newEvent, calendarId:"primary")
service.executeQuery(query, completionHandler: {(_ callbackTicket: GTLRServiceTicket, _ event: GTLRCalendar_Event, _ callbackError: Error?) -> Void in
print("executed query")
if callbackError == nil {
print("added")
print(newEvent.summary);
}
else {
print("add failed")
print(callbackError)
}
} as? GTLRServiceCompletionHandler)
I got this to work in Swift 4. I based it on the Java code example that Google has because that one was the most similar. I hope this answers all of your questions. I am sure there is a prettier way to do this, but I don't know it. :)
//Declares the new event
var newEvent: GTLRCalendar_Event = GTLRCalendar_Event()
//this is setting the parameters of the new event
newEvent.summary = ("Google I/O 2015")
newEvent.location = ("800 Howard St., San Francisco, CA 94103")
//I ran into some problems with the date formatting and this is what I ended with.
//Start Date. The offset adds time to the current time so if you run the program at 12:00 then it will record a time of 12:05 because of the 5 minute offset
let startDateTime: GTLRDateTime = GTLRDateTime(date: Date(), offsetMinutes: 5)
let startEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
startEventDateTime.dateTime = startDateTime
newEvent.start = startEventDateTime
print(newEvent.start!)
//Same as start date, but for the end date
let endDateTime: GTLRDateTime = GTLRDateTime(date: Date(), offsetMinutes: 50)
let endEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
endEventDateTime.dateTime = endDateTime
newEvent.end = endEventDateTime
print(newEvent.end!)
let service: GTLRCalendarService = GTLRCalendarService()
//The query
let query =
GTLRCalendarQuery_EventsInsert.query(withObject: newEvent, calendarId:"Past your calendar ID here this is specific to the calendar you want to edit and can be found under the google calendar settings")
//This is the part that I forgot. Specify your fields! I think this will change when you add other perimeters, but I need to review how this works more.
query.fields = "id";
//This is actually running the query you just built
self.service.executeQuery(
query,
completionHandler: {(_ callbackTicket:GTLRServiceTicket,
_ event:GTLRCalendar_Event,
_ callbackError: Error?) -> Void in}
as? GTLRServiceCompletionHandler
)
}
I was facing the same problem during the lack of resources at this topic, those are the steps
->configure your app with google calendar account
1-go to https://console.developers.google.com/ add a new project with app bundle id and name
2- go to dashboard click Enable APIS AND SERVICES then choose a calendar API Service and enable It.
3-choose credentials from the side menu and click CREATE CREDENTIALS Link from top of the page and add OAuth Client ID
4-open firebase console https://console.firebase.google.com/u/0/
5- click add project and choose your existing app and continue
6- follow the steps here https://firebase.google.com/docs/ios/setup until download "GoogleService-Info.plist" and add it to your app
-> write code to add an event to google calendar
1-follow those steps to add google sign-in
https://developers.google.com/identity/sign-in/ios/sign-in?ver=swift
2-
// Create an event to the Google Calendar's user
func addEventoToGoogleCalendar(summary : String, description :String, startTime : String, endTime : String) {
let calendarEvent = GTLRCalendar_Event()
calendarEvent.summary = "\(summary)"
calendarEvent.descriptionProperty = "\(description)"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy HH:mm"
let startDate = dateFormatter.date(from: startTime)
let endDate = dateFormatter.date(from: endTime)
guard let toBuildDateStart = startDate else {
print("Error getting start date")
return
}
guard let toBuildDateEnd = endDate else {
print("Error getting end date")
return
}
calendarEvent.start = buildDate(date: toBuildDateStart)
calendarEvent.end = buildDate(date: toBuildDateEnd)
let insertQuery = GTLRCalendarQuery_EventsInsert.query(withObject: calendarEvent, calendarId: "primary")
service.executeQuery(insertQuery) { (ticket, object, error) in
if error == nil {
print("Event inserted")
} else {
print(error)
}
}
}
// Helper to build date
func buildDate(date: Date) -> GTLRCalendar_EventDateTime {
let datetime = GTLRDateTime(date: date)
let dateObject = GTLRCalendar_EventDateTime()
dateObject.dateTime = datetime
return dateObject
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: UIAlertController.Style.alert
)
let ok = UIAlertAction(
title: "OK",
style: UIAlertAction.Style.default,
handler: nil
)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
here is the GitHub link https://github.com/emanShedeed/writeEventToGoogleCalendar

Resources