For my widget, I want users to be able to select a widget background color of their choice, but I can\'t seem to get it to work.
I\'m trying to do this with a Color
You are accessing UserDefaults in your Widget rather than in the widget's timeline provider. You are also storing your Color in an unnecessarily complicated way.
Here is a simple example that shows you how to save a UIColor into UserDefaults and access it in your widget. Although you are using Color, Color structs can be created from a UIColor. However ColorPicker
allows you to create a binding with a CGColor
or Color
, and CGColor
can be converted easily to UIColor
.
In my ContentView I have created a simple app that uses a ColorPicker with a binding of CGColor
. When the color is selected we pass it as a UIColor to the save function. This uses NSKeyedArchiver
to convert the UIColor into Data, which can easily be saved into UserDefaults. I use AppStorage
to store the Data created from the UIColor.
We then call WidgetCenter.shared.reloadAllTimelines()
to make sure that the WidgetCenter knows that we want to update the widgets.
import SwiftUI
import WidgetKit
struct ContentView: View {
@AppStorage("color", store: UserDefaults(suiteName: "group.com.my.app.identifier"))
var colorData: Data = Data()
@State private var bgColor: CGColor = UIColor.systemBackground.cgColor
var body: some View {
ColorPicker("Color", selection: Binding(get: {
bgColor
}, set: { newValue in
save(color: UIColor(cgColor: newValue))
bgColor = newValue
}))
}
func save(color: UIColor) {
do {
colorData = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false)
WidgetCenter.shared.reloadAllTimelines()
} catch let error {
print("error color key data not saved \(error.localizedDescription)")
}
}
}
Next in the Provider we use property wrapper AppStorage
to access the Data that we saved for the color. We do not access AppStorage
inside MyWidgetEntryView
or inside MyWidget
as it will not work there.
Inside my provider I have created a computed property that gets the UIColor from the color data that we stored in AppStorage
. This color is then passed to each entry when it is created. This is key to using AppStorage
with your widgets, the values must be passed when the entry is created.
MyWidgetEntryView is very simple it just shows the date that it was created and the background color.
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
@AppStorage("color", store: UserDefaults(suiteName: "group.com.my.app.identifier"))
var colorData: Data = Data()
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(color: color)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(color: color)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
let entry = SimpleEntry(color: color)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
var color: UIColor {
var color: UIColor?
do {
color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: colorData)
} catch let error {
print("color error \(error.localizedDescription)")
}
return color ?? .systemBlue
}
}
struct SimpleEntry: TimelineEntry {
let date: Date = Date()
let color: UIColor
}
struct MyWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
ZStack {
Color(entry.color)
Text(entry.date, style: .time)
}
}
}
@main
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
Here it is working