【问题标题】:SwiftUI widget not updating when @AppStorage value is changed@AppStorage 值更改时,SwiftUI 小部件不更新
【发布时间】:2021-04-26 05:21:12
【问题描述】:

我正在尝试让 SwiftUI 小部件在 @AppStorage 的值更改时根据命令进行更新。当我加载模拟器时,小部件从@AppStorage 更新为正确的值,但无论我尝试什么都不会再次更新。要在小部件中显示新值,需要关闭并重新打开模拟器。

在应用中查看:

import SwiftUI
import WidgetKit

struct MessageView: View {

    @AppStorage("message", store: UserDefaults(suiteName: "group.com.suiteName")) 
    var widgetMessage: String = ""

    var body: some View {
        VStack {
            Button(action: {
                self.widgetMessage = "new message"
                WidgetCenter.shared.reloadAllTimelines()
            }, label: { Text("button") })
        }

    }
}

小部件文件:

import WidgetKit
import SwiftUI
import Intents

struct Provider: IntentTimelineProvider {
    @AppStorage("message", store: UserDefaults(suiteName: "group.com.suiteName")) 
    var widgetMessage: String = ""
    
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), message: "Have a great day!", configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), message: "Have a great day!", configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let entry = SimpleEntry(date: Date(), message: widgetMessage, configuration: configuration)
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let message: String
    let configuration: ConfigurationIntent
}

struct statusWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        VStack{
            Text(entry.message)
        }
    }
}

@main
struct statusWidget: Widget {
    let kind: String = "StatusWidget"

    var body: some WidgetConfiguration {
        IntentConfiguration(
            kind: kind,
            intent: ConfigurationIntent.self,
            provider: Provider()
        ) { entry in
            statusWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Note Widget")
        .description("Display note from a friend or group")
    }
}

【问题讨论】:

    标签: swift swiftui widget widgetkit


    【解决方案1】:

    AppStorage 应该在视图中,其他所有内容都保留在条目中

    struct statusWidgetEntryView : View {
        @AppStorage("message", store: UserDefaults(suiteName: "group.com.suiteName")) 
        var widgetMessage: String = ""
    
        var entry: Provider.Entry
    
        var body: some View {
            VStack{
                Text(widgetMessage)
            }
        }
    }
    

    【讨论】:

    • 这有帮助,但我很确定问题来自 @AppStorage 存储的值未更新。在时间线中使用打印语句时,每次运行时都会打印与应用启动时相同的消息,即使值应该更新。
    【解决方案2】:

    AppStorage+Widget 方法存在两个基本问题:

    1. 您不能在 SwiftUI 之外使用 @AppStorage View - 尤其不能在 IntentTimelineProvider 中使用。
    2. 小部件视图是静态的 - 即使您在小部件视图中使用@AppStorage,该值也只会被读取一次(击败@AppStorage 的观点)。

    相反,您需要手动UserDefaults读取值:

    struct Provider: IntentTimelineProvider {
        let userDefaults = UserDefaults(suiteName: "group.com.suiteName")!
    
        func placeholder(in context: Context) -> SimpleEntry {
            SimpleEntry(date: Date(), message: "Have a great day!", configuration: ConfigurationIntent())
        }
    
        func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
            let entry = SimpleEntry(date: Date(), message: "Have a great day!", configuration: configuration)
            completion(entry)
        }
    
        func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    
            // read on every timeline refresh
            let widgetMessage = userDefaults.string(forKey: "message") ?? ""
    
            let entry = SimpleEntry(date: Date(), message: widgetMessage, configuration: configuration)
            let timeline = Timeline(entries: [entry], policy: .never)
            completion(timeline)
        }
    }
    

    【讨论】:

    • 感谢您的帮助。我应用了您的建议,并验证了该应用程序是否正确保存和读取了新值。但是,该小部件在启动时仍然只读取一次,并且每次更新都具有旧值。
    • 是否将@AppStorage 限制在视图中会引入另一个与 SwiftUI 模型对立的事实来源?至少,现在有必要将真实信息仅存储在 View 中,然后将其传递给您的其他真实来源,这与 SwiftUI 模式相反。
    【解决方案3】:

    您需要确保在调用WidgetCenter.shared.reloadAllTimelines() 之前将新值写入磁盘,我认为@AppStorage 没有办法做到这一点。在您的应用中,尝试直接设置 UserDefaults,然后在重新加载小部件之前调用 synchronize()

    Button(action: {
       let userDefaults = UserDefaults(suiteName: "group.com.suiteName")!
       userDefaults.set("new message", forKey: "message")
       userDefaults.synchronize()
       WidgetCenter.shared.reloadAllTimelines()
    }, label: { Text("button") })
    

    我知道文档声称 synchronize() 不再需要,但它是唯一对我有用的东西。 ¯\_(ツ)_/¯

    在您的小部件中使用 UserDefaults 而不是 @AppStorage 也可能会有所帮助。

    【讨论】:

    • 感谢您的建议,但小部件仍未更新。我一直在网上浏览一堆例子,每个人似乎都在让它以类似的方式工作。不知道我错过了什么。
    【解决方案4】:

    确保将您的应用组添加到签名和功能中的两个目标

    【讨论】:

      猜你喜欢
      • 2013-05-05
      • 1970-01-01
      • 2021-11-22
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      • 1970-01-01
      • 1970-01-01
      • 2020-12-06
      相关资源
      最近更新 更多