如何从应用更新 iOS 14 Widget 背景颜色?
How to update iOS 14 Widget background color from the app?
对于我的小部件,我希望用户能够 select 他们选择的小部件背景颜色,但我似乎无法让它工作。
我正在尝试使用 ColorPicker
和 @AppStorage
ColorPicker("Background Color", selection: Binding(get: {
bgColor
}, set: { newValue in
backgroundColor = self.updateColorInAppStorage(color: newValue)
bgColor = newValue
}))
.frame(width: 200, height: 50)
.font(.headline)
backgroundColor 是一个 @AppStorage
变量,用于保存 RGB 值
在我的小部件扩展 class 中,我使用这个变量在 @main 结构中设置小部件背景颜色:
@main
struct MyWidget: Widget {
@AppStorage("backgroundColor", store: UserDefaults(suiteName: "group.com.MyWidget")) var backgroundColor = ""
@State private var bgColor = Color(UIColor.systemBackground)
let kind: String = "Count"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
SubscriberCountEntryView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(bgColor)
.onAppear {
if (backgroundColor != "") {
let rgbArray = backgroundColor.components(separatedBy: ",")
if let red = Double(rgbArray[0]), let green = Double(rgbArray[1]), let blue = Double(rgbArray[2]), let alpha = Double(rgbArray[3]) {
bgColor = Color(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
}
}
}
}
.configurationDisplayName("Count")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
这似乎只有在设备上首次安装该应用程序时才有效。之后,无论我选择颜色并更新小部件多少次,背景颜色都不会改变。如果我删除并重新安装应用程序,颜色会改变。
关于如何正确执行此操作的任何想法?
您正在您的小部件中访问 UserDefaults,而不是在小部件的时间线提供程序中。您还以不必要的复杂方式存储了 Color。
这是一个简单的示例,向您展示了如何将 UIColor 保存到 UserDefaults 中并在您的小部件中访问它。尽管您使用的是 Color,但可以从 UIColor 创建 Color 结构。但是 ColorPicker
允许您使用 CGColor
或 Color
创建绑定,并且 CGColor
可以轻松转换为 UIColor
。
内容视图
在我的 ContentView 中,我创建了一个简单的应用程序,它使用绑定了 CGColor
的 ColorPicker。选择颜色后,我们将其作为 UIColor 传递给保存函数。这使用 NSKeyedArchiver
将 UIColor 转换为 Data,可以轻松将其保存到 UserDefaults 中。我使用 AppStorage
来存储从 UIColor 创建的数据。
然后我们调用 WidgetCenter.shared.reloadAllTimelines()
以确保 WidgetCenter 知道我们要更新小部件。
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)")
}
}
}
我的小部件
接下来在 Provider 中我们使用 属性 包装器 AppStorage
来访问我们为颜色保存的数据。我们不会在 MyWidgetEntryView
或 MyWidget
内访问 AppStorage
,因为它在那里不起作用。
在我的提供程序中,我创建了一个计算 属性,它从我们存储在 AppStorage
中的颜色数据中获取 UIColor。然后在创建时将此颜色传递给每个条目。这是将 AppStorage
与小部件一起使用的关键,必须在创建条目时传递值。
MyWidgetEntryView 非常简单,它只显示创建日期和背景颜色。
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.")
}
}
正在运行
对于我的小部件,我希望用户能够 select 他们选择的小部件背景颜色,但我似乎无法让它工作。
我正在尝试使用 ColorPicker
和 @AppStorage
ColorPicker("Background Color", selection: Binding(get: {
bgColor
}, set: { newValue in
backgroundColor = self.updateColorInAppStorage(color: newValue)
bgColor = newValue
}))
.frame(width: 200, height: 50)
.font(.headline)
backgroundColor 是一个 @AppStorage
变量,用于保存 RGB 值
在我的小部件扩展 class 中,我使用这个变量在 @main 结构中设置小部件背景颜色:
@main
struct MyWidget: Widget {
@AppStorage("backgroundColor", store: UserDefaults(suiteName: "group.com.MyWidget")) var backgroundColor = ""
@State private var bgColor = Color(UIColor.systemBackground)
let kind: String = "Count"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
SubscriberCountEntryView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(bgColor)
.onAppear {
if (backgroundColor != "") {
let rgbArray = backgroundColor.components(separatedBy: ",")
if let red = Double(rgbArray[0]), let green = Double(rgbArray[1]), let blue = Double(rgbArray[2]), let alpha = Double(rgbArray[3]) {
bgColor = Color(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
}
}
}
}
.configurationDisplayName("Count")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
这似乎只有在设备上首次安装该应用程序时才有效。之后,无论我选择颜色并更新小部件多少次,背景颜色都不会改变。如果我删除并重新安装应用程序,颜色会改变。
关于如何正确执行此操作的任何想法?
您正在您的小部件中访问 UserDefaults,而不是在小部件的时间线提供程序中。您还以不必要的复杂方式存储了 Color。
这是一个简单的示例,向您展示了如何将 UIColor 保存到 UserDefaults 中并在您的小部件中访问它。尽管您使用的是 Color,但可以从 UIColor 创建 Color 结构。但是 ColorPicker
允许您使用 CGColor
或 Color
创建绑定,并且 CGColor
可以轻松转换为 UIColor
。
内容视图
在我的 ContentView 中,我创建了一个简单的应用程序,它使用绑定了 CGColor
的 ColorPicker。选择颜色后,我们将其作为 UIColor 传递给保存函数。这使用 NSKeyedArchiver
将 UIColor 转换为 Data,可以轻松将其保存到 UserDefaults 中。我使用 AppStorage
来存储从 UIColor 创建的数据。
然后我们调用 WidgetCenter.shared.reloadAllTimelines()
以确保 WidgetCenter 知道我们要更新小部件。
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)")
}
}
}
我的小部件
接下来在 Provider 中我们使用 属性 包装器 AppStorage
来访问我们为颜色保存的数据。我们不会在 MyWidgetEntryView
或 MyWidget
内访问 AppStorage
,因为它在那里不起作用。
在我的提供程序中,我创建了一个计算 属性,它从我们存储在 AppStorage
中的颜色数据中获取 UIColor。然后在创建时将此颜色传递给每个条目。这是将 AppStorage
与小部件一起使用的关键,必须在创建条目时传递值。
MyWidgetEntryView 非常简单,它只显示创建日期和背景颜色。
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.")
}
}
正在运行