Briefly explained; I am working on a project for my Tesla car. Tesla already has a widget that only can be added to the Today View tab, and that widget automatically refreshes when I swipe to to the today view. This is what it looks like: Image here
I want to accomplish the same thing in my widget. I basically have the working code, look below:
NB: This project of mine is for experimental and personal use only.
extension Date {
func timeAgoDisplay() -> String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: self, relativeTo: Date())
}
}
import WidgetKit
import SwiftUI
import Intents
import TeslaSwift
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, 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: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
getVehicle() // Run this function to get vehicle info
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
var lastUpdated = String()
var batteryLevel = Int()
var interiorTemperature = Double()
func getVehicle() {
let apii = TeslaSwift()
if let jsonString = UserDefaults(suiteName: "group.widget")!.string(forKey: "GlobalToken"),
let token: AuthToken = jsonString.decodeJSON(),
let _ = UserDefaults(suiteName: "group.widget")!.string(forKey: "GlobalToken") {
apii.reuse(token: token, email: nil)
}
apii.useMockServer = false
apii.debuggingEnabled = true
let id = UserDefaults(suiteName: "group.widget")!.string(forKey: "GlobalSelectedID")
apii.getVehicle(id!).done {
(vehicle: Vehicle) -> Void in
apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
batteryLevel = (extendedVehicle.chargeState?.batteryLevel)!
interiorTemperature = (extendedVehicle.climateState?.insideTemperature!.celsius)!
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy - HH:mm:ss"
let now = Date()
let dateString = formatter.string(from:now)
lastUpdated = dateString
}.catch { (error) in
print("error1: \(error)")
}
}.catch { error in
print("error2: \(error)")
}
}
struct PWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Battery: \(batteryLevel)%")
Text("Temparature: \(String(format: "%.0f", interiorTemperature))")
Text("Last Updated: \(lastUpdated)")
.environment(\.sizeCategory, .extraSmall)
}
}
}
@main
struct PWidget: Widget {
let kind: String = "PWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
PWidgetEntryView(entry: entry)
}
.supportedFamilies([.systemMedium])
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct PWidget_Previews: PreviewProvider {
static var previews: some View {
PWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
.previewContext(WidgetPreviewContext(family: .systemMedium))
}
}
So now, when using this code. It fetch the data perfectly, but the widget does not display the data.
If I add WidgetCenter.shared.reloadAllTimelines()
after the lastUpdated = dateString
, the widget updates, but also keeps updating about every single five seconds. That will draw a huge amount of battery.
I have also tried by adding var didUpdateManually = false
outside of the func getVehicle() {
and then a if false check like this. That makes it update the widget once, but never ever again:
if (didUpdateManually == false) {
WidgetCenter.shared.reloadAllTimelines()
didUpdateManually = true
}
So basically there are two/three things I am trying to accomplish:
1. Display the value from API to my widget (batteryLevel, interiorTemperature and lastUpdated timestamp).
2. If either or both is possible:
2.A: When the widget is added to the Today View tab, I want to automatically update the widget by re-running the `func getVehicle()` and update the info when the user swipe to the Today View tab.
2.B: If the widget is on the home screen page, I want to widget to automatically update when the in the same way as 2A, or update once every hour or so.