Wigets
Well, I made a widget. Two, to be exact; a medium and large widget.
The CWC+ WidgetKit course got me through most of what I wanted to do with the widgets. However, there were afew things I had to figure out on my own.
The data source used in the course is pulling data from an API, which is unlike Liquidus that uses an API.
I landed on passing my View Model to the Widget, since on creation it pulls data from UserDefaults. Plus I had the benefit of being able to easily use all the functions in my View Model.
In hindsight, I’m not sure how necessary this was or not, but I created an App Group. These let you share data between your app and a widget (or other extensions).
This was my orginal code:
if let data = UserDefaults.standard.data(forKey: Constants.savedKey) {
if let decoded = try? JSONDecoder().decode(DrinkData.self, from: data) {
self.drinkData = decoded
return
}
}
With an App Group it changes to this, with the suite name being defined by the developer:
if let userDefaults = UserDefaults(suiteName: "group.com.cengelbart.Liquidus.shared") {
if let data = userDefaults.data(forKey: Constants.savedKey) {
if let decoded = try? JSONDecoder().decode(DrinkData.self, from: data) {
self.drinkData = decoded
return
}
}
}
Say you want to update your widgets after a change occurs in the app. You can use this line of code in your main app: WidgetCenter.shared.reloadAllTimelines()
. You’ll have to import WidgetCenter
though.
There’s also WidgetCenter.shared.reloadTimelines(ofKind:)
, for if you have multiple widgets types.
The other thing that came up was a configurable widget. In Liquidus, you can view your intake data by the day or by the week. With the widgets, I wanted this same option.
To figure this out I used these resources:
For any configuration you need a SiriKit Intent Definition File. Since the time period can either be by day or by week and only these values, I can create an Enum in the definition file as my list of options is not dynamic. Then I just have to tell the definition file to take in a value from Enum.
First, since I started from a Static Configuration, I changed my widget to be a Intent Configuration
Then for my SimpleEntry
, I added these properties:
relevance: TimelineEntryRelevance
timePeriod: String
From here we need to update the Provider
struct, since I started with a Static, non-configurable, Provider. The provider has to conform to the IntentTimelineProvider
Protocol. The getSnapshot()
and getTimeline()
now have an additional for configuration: _
parameter.
In the Provider
struct we also have to add a function that converts the value of the Intent to a value to use in the entry, since I used an Enum. Which for me looked like this:
private func timePeriod(for configuration: TimePeriodSelectionIntent) -> String {
switch configuration.timePeriod {
case .day:
return Constants.selectDay
case .week:
return Constants.selectWeek
default:
return Constants.selectDay
}
}
In the getTimeline()
function of Provider
, we now have to pass in a relevance to the entry. This dictates when its important to show your widget, if it’s in a Smart Stack, to the user. To get the relevance we need this: TimelineEntryRelevance(score: _)
. score
is the numerical value of the relevance. The higher the number, the more important it is. I decided to dictate this based on how close the user is too their daily or weekly goal. The bigger the difference, the higher relevance.
The above resources are great for at least figuring out how to fill a Intent Definition file and what changes you need to make. From what I can tell this process is a bit more complicated if you have a dynamic list of options. For instance, if I wanted to let the user sort between drink types on the widget. This list won’t be static since the user can add and remove from it.
Now, we can choose and see daily/weekly data:
Since (I think) I have some of the groundwork for Siri Shortcuts, I will take this on next. The functionality of the shortcut will be like the widgets, to view daily/weekly intake.