How to update iOS 14 Widget background color from the app?

↘锁芯ラ 提交于 2020-12-26 08:34:19

问题


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 ColorPicker and @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 is an @AppStorage variable that holds the RGB values

In my Widget extension class, I am using this variable to set the widget background color in the @main struct:

@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])
    }
}

This only seems to be working when the app is first installed on the device. After that, no matter how many times I choose a color and update the widget, the background color won't change. If I delete and install the app again, the color changes.

Any ideas on how to do this properly?


回答1:


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 😀



来源:https://stackoverflow.com/questions/64724487/how-to-update-ios-14-widget-background-color-from-the-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!