How to update iOS 14 Widget background color from the app?
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
.
ContentView
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)")
}
}
}
MyWidget
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<Entry>) -> ()) {
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 /p>
Can't change the iOS14 widget background color
You need to specify full frame, as on below demo
StaticConfiguration(
kind: "xWidget",
provider: xProvider(),
placeholder: Text("Loading...")) { entry in
xWidgetEntryView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity) // << here !!
.background(Color.green)
}.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
How do I make my widget update more frequently or update when an action happens within the app
You can always update your widget from your app with the code block below. Please be aware you need to import WidgetKit
to your class for calling this function.
WidgetCenter.shared.reloadAllTimelines()
iOS widget background-image goes black after couple of minutes
Since the problem only occurs with these two specific images it seems like the files are either
- Not correctly added to the assets folder
- Broken or corrupt
Try to generate a completely new file for both pictures (e.g. take screenshots) and replace them with the current images in the assets folder. That should most likely fix your issue.
Related Topics
Uibezierpath Subclass Initializer
Change Duration (Speed) on a Running Animation
Swift: Sort Array by Sort Descriptors
iOS Sound Not Playing in Swift
How to Integrate Fitbit API in iOS App Using Swift
Swift: Nil Is Incompatible with Return Type String
iOS Swift Remove Uitableview Cell Separator Space
How to Use Tap Gesture in Accessibility in Swift
Animate a Change in Part of an Nsmutableattributedstring
How to Turn Off Core Data Write-Ahead Logging in Swift Using Options Dictionary
Routing Between Points with Mapbox
What Is a Safe Way to Turn Streamed (Utf8) Data into a String
How to Instantiate and Load a View Controller Before Segueing to It Using Swift
Swift - Nsdate and Last Week of Year
Type 'Any' Has No Subscript Members in Swift 3 Xcode 8
App Installation Failed: Unknown Error Xcode 7
Table View Controller Duplicate Itself in iOS Swift
Indexing into Array of Functions: Expression Resolves to an Unused L-Value