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

后端 未结 1 1654
余生分开走
余生分开走 2021-01-23 03:21

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

相关标签:
1条回答
  • 2021-01-23 03:43

    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

    0 讨论(0)
提交回复
热议问题