Passing data from an App VC to its widget

Learning Widgets, I try a simple pattern:
  • get a textField in the app;

  • have the Widget updated when textField is changed.

App is UIKit.
Widget has No "included Configuration Intent".

It compiles and works. Widget is updated every 5 minutes as asked line 23 (final snippet).
But the message is never updated (line 48 of final snippet) with globalToPass (as expected from line 24): it always shows "Hello".

What I tried:
  • Create a singleton to hold the data to pass:

Code Block
class Util {
class var shared : Util {
struct Singleton {
static let instance = Util()
}
return Singleton.instance;
}
var globalToPass = "Hello"
}
  • shared the file between the 2 targets App and WidgetExtension

  • In VC, update the singleton when textField is changed and ask for widget to reload timeline

Code Block
@IBAction func updateMessage(_ sender: UITextField) {
Util.shared.globalToPass = valueToPassLabel.text ?? "--"
WidgetCenter.shared.reloadTimelines(ofKind: "WidgetForTest")
WidgetCenter.shared.reloadAllTimelines()
}

Problem : Widget never updated its message field

Probably I need to have the message in @State var, but I could not get it work.
  • Here is the full widget code at this time:

Code Block
import WidgetKit
import SwiftUI
struct LoadStatusProvider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), loadEntry: 0, message: Util.shared.globalToPass)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), loadEntry: 0, message: Util.shared.globalToPass)
completion(entry)
}
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 minuteOffset in 0 ..< 2 {
let entryDate = Calendar.current.date(byAdding: .minute, value: 5*minuteOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, loadEntry: minuteOffset, message: Util.shared.globalToPass)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let loadEntry: Int
let message: String
}
struct WidgetForTestNoIntentEntryView : View {
var entry: LoadStatusProvider.Entry
var body: some View {
let formatter = DateFormatter()
formatter.timeStyle = .medium
let dateString = formatter.string(from: entry.date)
return
VStack {
Text(String(entry.message))
HStack {
Text("Started")
Text(entry.date, style: .time)
}
HStack {
Text("Now")
Text(dateString)
}
HStack {
Text("Loaded")
Text(String(entry.loadEntry))
}
}
}
}
@main
struct WidgetForTestNoIntent: Widget {
let kind: String = "WidgetForTestNoIntent"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: LoadStatusProvider()) { entry in
WidgetForTestNoIntentEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct WidgetForTestNoIntent_Previews: PreviewProvider {
static var previews: some View {
WidgetForTestNoIntentEntryView(entry: SimpleEntry(date: Date(), loadEntry: 0, message: "-"))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
  • I have not defined extension for IntentHandler

Answered by Mcrich23 in 668396022
You pretty much have to. I use:

Code Block
import UIKit
import WidgetKit
...
//code
//code for widget
UDM.shared.UserDefaults(suiteName: "group.whatever")?.setValue(urCode, forKey: "I'm a key")
WidgetCenter.shared.reloadAllTimelines()

Note: you have to set up a shared group between the app and the widget in the capabilities of the targets.

I read a solution by saving in userDefaults.
https://stackoverflow.com/questions/64240439/how-to-pass-uiviewcontroller-data-to-swiftui-widget-class

But I would prefer not having to save, just using singleton if possible.
Accepted Answer
You pretty much have to. I use:

Code Block
import UIKit
import WidgetKit
...
//code
//code for widget
UDM.shared.UserDefaults(suiteName: "group.whatever")?.setValue(urCode, forKey: "I'm a key")
WidgetCenter.shared.reloadAllTimelines()

Note: you have to set up a shared group between the app and the widget in the capabilities of the targets.

Passing data from an App VC to its widget
 
 
Q