当@AppStorage的值改变时,SwiftUI小部件不会更新

8
我正在尝试让一个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")
    }
}
4个回答

4

在调用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可能会有所帮助。

谢谢您的建议,但小部件仍然没有更新。我已经查看了许多在线示例,每个人似乎都以类似的方式使其工作。不确定我错过了什么。 - Jon T

4

请确保在“签名和能力”中将您的应用程序组添加到两个目标中


谢谢你的回答。我完全忘记了在复制代码时之前的应用程序中设置了这个。群组用户默认值似乎在我的应用程序中工作,但实际上并没有将它们存储在共享空间中。一旦我为每个目标添加了签名和证书下的应用程序组部分,它就开始按预期工作了。 - MikeMilzz

0

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存储的值没有更新。在时间轴中使用打印语句时,每次运行它都会打印与应用程序启动时相同的消息,尽管该值应该已更新。 - Jon T

0

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

  1. 无法在SwiftUI View之外使用@AppStorage,特别是在IntentTimelineProvider中。
  2. Widget视图是静态的 - 即使在widget视图中使用@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)
    }
}

感谢您的帮助。我采纳了您的建议,并验证了应用程序正确保存和读取新值。然而,小部件仍然只在启动时读取一次,每次更新都具有旧值。 - Jon T
将@AppStorage限制在View中是否引入了另一个真相来源,这与SwiftUI模型背道而驰?至少现在,它使得必须仅在View中存储真相信息,然后将其传递给其他真相来源,违反了SwiftUI的模式。 - promacuser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接