如何从应用程序更新 iOS 14 Widget 背景色?

Arj*_*un 3 ios swift widgetkit swiftui

对于我的小部件,我希望用户能够选择他们选择的小部件背景颜色,但我似乎无法让它工作。

我正在尝试用一个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)
Run Code Online (Sandbox Code Playgroud)

backgroundColor 是一个@AppStorage保存 RGB 值的变量

在我的 Widget 扩展类中,我使用这个变量在 @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])
    }
}
Run Code Online (Sandbox Code Playgroud)

这似乎仅在应用程序首次安装在设备上时才有效。之后,无论我选择颜色并更新小部件多少次,背景颜色都不会改变。如果我再次删除并安装该应用程序,颜色会发生变化。

关于如何正确执行此操作的任何想法?

And*_*rew 6

您是在您的小部件中而不是在小部件的时间线提供者中访问 UserDefaults。您还在以一种不必要的复杂方式存储您的颜色。

这是一个简单的示例,向您展示如何将 UIColor 保存到 UserDefaults 并在您的小部件中访问它。尽管您使用的是 Color,但可以从 UIColor 创建颜色结构。但是,ColorPicker允许您使用CGColor或来创建绑定Color,并且CGColor可以轻松转换为UIColor

内容视图

在我的 ContentView 中,我创建了一个简单的应用程序,它使用了一个绑定为CGColor. 选择颜色后,我们将其作为 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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的小部件

接下来在 Provider 中,我们使用属性包装器AppStorage来访问我们为颜色保存的数据。我们不进入AppStorage内部MyWidgetEntryView或内部,MyWidget因为它在那里不起作用。

在我的提供程序中,我创建了一个计算属性,该属性从我们存储在AppStorage. 然后在创建时将此颜色传递给每个条目。这是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.")
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里它正在工作

带有颜色选择器的小部件