ios 尽管调用了reloadAllTimelines,但SwiftUI微件未重新加载

rjzwgtxy  于 2023-03-20  发布在  iOS
关注(0)|答案(3)|浏览(150)

我有一个小部件,它可以显示我在应用程序中修改的核心数据,我只希望这个小部件在主应用程序中的核心数据库刷新时刷新。
这是我的小部件

//
//  RemindMeWidget.swift
//  RemindMeWidget
//
//  Created by Charel Felten on 05/07/2021.
//

import WidgetKit
import SwiftUI
import Intents
import CoreData


struct Provider: IntentTimelineProvider {
  func placeholder(in context: Context) -> SimpleEntry {
    SimpleEntry(
      date: Date(),
      configuration: ConfigurationIntent(),
      notes: Note.previewNotes,
      realFamily: context.family
    )
  }
  
  func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    let entry = SimpleEntry(
      date: Date(),
      configuration: configuration,
      notes: Note.previewNotes,
      realFamily: context.family
    )
    completion(entry)
  }
  
  func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    
    print("get timeline is called")
    
    var notes: [Note] = []
    
    let containerURL = PersistenceController.containerURL
    let storeURL = containerURL.appendingPathComponent(PersistenceController.SQLiteStoreAppendix)
    let description = NSPersistentStoreDescription(url: storeURL)
    let container = NSPersistentCloudKitContainer(name: PersistenceController.containerName)
    
    container.persistentStoreDescriptions = [description]
    
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
    
    let viewContext = PersistenceController.shared.container.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
    do {
      let result = try viewContext.fetch(request)
      // is there a nicer way to do it?
      if let tmp = result as? [Note] {
        notes = tmp
      }
    } catch {
      let nsError = error as NSError
      fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
    }
    
    notes = notes.sorted { (lhs, rhs) in
      if lhs.int16priority == rhs.int16priority {
        return lhs.timestamp! > rhs.timestamp!
      }
      return lhs.int16priority > rhs.int16priority
    }
    
    let entry = SimpleEntry(
      date: Date(),
      configuration: configuration,
      notes: notes,
      realFamily: context.family
    )
    print("hey")
    let timeline = Timeline(entries: [entry], policy: .atEnd)
    completion(timeline)
  }
}


struct SimpleEntry: TimelineEntry {
  let date: Date
  let configuration: ConfigurationIntent
  var notes: [Note]
  var realFamily: WidgetFamily
}


struct RemindMeWidgetEntryView : View {
  var entry: Provider.Entry
  
  var body: some View {
    WidgetView(notes: entry.notes, realFamily: entry.realFamily)
  }
}


@main
struct RemindMeWidget: Widget {
  let kind: String = "RemindMeWidget"
  
  var body: some WidgetConfiguration {
    IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
      RemindMeWidgetEntryView(entry: entry)
    }
    .configurationDisplayName("My Widget")
    .description("This is an example widget.")
  }
}


struct RemindMeWidget_Previews: PreviewProvider {
  static var previews: some View {
    RemindMeWidgetEntryView(
      entry: SimpleEntry(
        date: Date(),
        configuration: ConfigurationIntent(),
        notes: Note.previewNotes,
        realFamily: .systemSmall
      )
    ).previewContext(WidgetPreviewContext(family: .systemSmall))
  }
}

在应用程序中,我有一个设置页面,在那里我调用手动小部件刷新(现在用于调试),如下所示:

Button("Reload Widget") {
            print("calling widgetcenter")
            WidgetCenter.shared.reloadAllTimelines()
            WidgetCenter.shared.getCurrentConfigurations({result in print(result)})
          }

完整代码:

//
//  SettingsView.swift
//  RemindMe
//
//  Created by Charel Felten on 02/07/2021.
//

import SwiftUI
import WidgetKit

struct SettingsView: View {
  
  @Environment(\.colorScheme) var colorScheme
  @ObservedObject var config: Config
  
  var body: some View {
    NavigationView {
      Form {
        ForEach(Priority.allRegularCases) { priority in
          PriorityView(config: config, priority: priority)
        }
        Section(header: Text("Theme")) {
          Picker(selection: $config.colorTheme, label: Text("Theme")) {
            ForEach(ColorTheme.allCases, id: \.self) { value in
              Text(value.rawValue).tag(value)
            }
          }
        }
        Section(header: Text("Additional Info"), footer: Text("Toggle which additional information to show below each note in the list.")) {
          Toggle(isOn: $config.showNotificationTime) {
            Text("Show reminders")
          }
          Toggle(isOn: $config.showCreationTime) {
            Text("Show date")
          }
          Button("Reload Widget") {
            print("calling widgetcenter")
            WidgetCenter.shared.reloadAllTimelines()
            WidgetCenter.shared.getCurrentConfigurations({result in print(result)})
          }
        }
      }
      .navigationTitle("Settings")
    }
    // save the settings if we leave the app (goes away from foreground)
    .onReceive(
      NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification),
      perform: { _ in config.save() }
    )
    .onDisappear(perform: Note.updateAllNotes)
  }
}

struct PriorityView: View {
  @Environment(\.colorScheme) var colorScheme
  @ObservedObject var config: Config
  var priority: Priority
  
  var body: some View {
    Section(header: Text(priority.getDescription()).foregroundColor(Color.secondary)) {
      Picker(selection: $config.priorityIntervals[priority.getIndex()], label: Text("Notification interval")) {
        ForEach(Interval.allCases, id: \.self) { value in
          Text(value.rawValue).tag(value)
        }
      }
      switch config.priorityIntervals[priority.getIndex()] {
      case .ten_minutes, .never:
        EmptyView()
      default:
        DatePicker("Reminders at", selection: $config.priorityDates[priority.getIndex()], displayedComponents: [.hourAndMinute])
      }
    }
    .foregroundColor(Colors.getColor(for: priority, in: .primary))
    .listRowBackground(
      ZStack {
        colorScheme == .dark ? Color.black : Color.white
        Colors.getColor(for: priority, in: .background)
      }
    )
  }
}

struct SettingsView_Previews: PreviewProvider {
  static var previews: some View {
    SettingsView(
      config: Config()
    )
  }
}

单击此按钮可将以下内容打印到我的控制台:

calling widgetcenter
success([WidgetInfo:
- configuration: <ConfigurationIntent: 0x282e51dd0> {
}
- family: systemSmall
- kind: RemindMeWidget, WidgetInfo:
- configuration: <ConfigurationIntent: 0x282e51e60> {
}
- family: systemLarge
- kind: RemindMeWidget, WidgetInfo:
- configuration: <ConfigurationIntent: 0x282e51ef0> {
}
- family: systemMedium
- kind: RemindMeWidget])

但是,该小部件不会刷新,其外观如下所示:

有时候我确实刷新了,但是当我在应用程序中进行更改时,小部件没有更新。**我在getTimeline中输入的print语句都没有被打印出来。**我真的很累,不知道我做错了什么...
我很抱歉发布了这么多代码,而不是一个较小的可复制的例子,但我只是不知道这个问题可能来自哪里。
我看了很多相关的问题,但是没有一个有帮助

如果你想看看代码,这里是:
https://github.com/charelF/RemindMeApp/tree/main/RemindMeWidget

小更新

它在模拟器中也不起作用。
我将所有更改都推送到了github,请参见https://github.com/charelF/RemindMeApp
我开了一个赏金,将奖励给谁帮助我找到我的问题。

k3bvogb1

k3bvogb11#

您的代码看起来不错,但有一个例外
RemindMeWidget.intentdefinition中,您定义了一个自定义Intent“Configuration”,但此Intent没有参数。
根据docu,Xcode生成以下自定义Intent:

public class ConfigurationIntent: INIntent {
}

由于Intent没有参数,因此该类没有任何@NSManaged public var
文件继续:
当WidgetKit调用getSnapshot(for:in:completion:)或getTimeline(for:in:completion:)时,它会传递一个自定义INIntent的示例,并使用用户选择的详细信息进行配置。游戏小部件提供程序访问Intent的属性,并将其包含在TimelineEntry中。然后,WidgetKit调用小部件配置的内容闭包,传递时间线条目,以允许视图访问用户配置的属性。
我大胆的猜测是,WidgetKit之所以混乱,是因为它没有找到任何要传递的参数。
我建议您先在没有Intent的情况下测试代码,如果这样做有效,请向Intent添加一个伪参数。
或许这能帮上忙...

ctehm74n

ctehm74n2#

这是苹果的意图。在苹果的文件中,它说:
重新加载小部件会消耗系统资源,并由于额外的网络和处理而导致电池耗尽。若要减少这种性能影响并保持全天电池续航时间,请将请求更新的频率和数量限制在必要的范围内。
为了管理系统负载,WidgetKit使用预算来分配一天中的小部件重新加载。预算分配是动态的,并考虑了许多因素
widget的预算适用于24小时的时段。WidgetKit根据用户的日常使用模式调整24小时窗口,这意味着日常预算不一定会在午夜重置。对于用户经常查看的widget,日常预算通常包括40到70次刷新。这个速度大致相当于widget每15到60分钟重新加载一次。但由于涉及到许多因素,这些间隔通常会发生变化。
Keeping a Widget Up To Date

chhkpiq4

chhkpiq43#

我设法用ChatGPT重构了一下代码,使它工作起来。我仍然不知道确切的错误是什么。
我改变了什么

  • 我换了静态意图,谢谢莱因哈德
  • 我将核心数据检索逻辑从timelineprovider移到一个单独的函数中,然后由timelineprovider和getSnapchot函数调用该函数。
  • 我的小部件视图的一些更改

我认为是我的bug(或者至少是一个bug,似乎是Owen报告的bug,即我的小部件总是显示一个空列表):
在我的widgetview中,我只想显示前4个注解,因为我无法在小部件中容纳更多注解。

SystemWidgetView(notes: Array(notes[..<4]))

ChatGPT建议此代码只显示4条注解:

SystemWidgetView(notes: Array(notes.prefix(4)))

我仍然不知道为什么一个是错的,另一个不是,但这似乎阻止了我的小部件显示。然而,我设法让他们显示有时甚至用我的老方法做它,所以我认为有额外的错误。ChatGPT还建议传递entry对象,而不是notes列表,不确定是否有影响。
另一个bug可能是我的视图文件中有这样的代码:

extension WidgetFamily: EnvironmentKey {
    public static var defaultValue: WidgetFamily = .systemMedium
}


extension EnvironmentValues {
  var widgetFamily: WidgetFamily {
    get { self[WidgetFamily.self] }
    set { self[WidgetFamily.self] = newValue }
  }
}

我不太清楚它做了什么,但我想它可能也有影响。
不管怎样,小部件是工作的。下面是工作代码:

RemindMeWidget.swift

//
//  RemindMeWidget.swift
//  RemindMeWidget
//
//  Created by Charel Felten on 05/07/2021.
//

import WidgetKit
import SwiftUI
import CoreData

struct MyWidgetProvider: TimelineProvider {
  typealias Entry = MyWidgetEntry
  
  func placeholder(in context: Context) -> MyWidgetEntry {
    MyWidgetEntry(date: Date(), notes: [])
  }
  
  func getSnapshot(in context: Context, completion: @escaping (MyWidgetEntry) -> Void) {
    let entry = MyWidgetEntry(date: Date(), notes: fetchNotes())
    completion(entry)
  }
  
  func getTimeline(in context: Context, completion: @escaping (Timeline<MyWidgetEntry>) -> Void) {
    let entry = MyWidgetEntry(date: Date(), notes: fetchNotes())
    let timeline = Timeline(entries: [entry], policy: .atEnd)
    completion(timeline)
  }
  
  private func fetchNotes() -> [Note] {
    var notes: [Note] = []
    let containerURL = PersistenceController.containerURL
    let storeURL = containerURL.appendingPathComponent(PersistenceController.SQLiteStoreAppendix)
    let description = NSPersistentStoreDescription(url: storeURL)
    let container = NSPersistentCloudKitContainer(name: PersistenceController.containerName)
    container.persistentStoreDescriptions = [description]
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
    let viewContext = PersistenceController.shared.container.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
    do {
      let result = try viewContext.fetch(request)
      if let tmp = result as? [Note] {
        notes = tmp
      }
    } catch {
      let nsError = error as NSError
      fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
    }
    notes = notes.sorted { (lhs, rhs) in
      if lhs.int16priority == rhs.int16priority {
        return lhs.timestamp! > rhs.timestamp!
      }
      return lhs.int16priority > rhs.int16priority
    }
    return notes
  }
}

struct MyWidgetEntry: TimelineEntry {
    let date: Date
    let notes: [Note]
}

@main
struct MyWidget: Widget {
    let kind: String = "RemindMeWidget"
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: MyWidgetProvider()) { entry in
          WidgetView(entry: entry)
        }
        .configurationDisplayName("RemindMe Widget")
        .description("Your most important reminders at a glance.")
        .supportedFamilies([
          .systemSmall,
          .systemMedium,
          .systemLarge,
          .systemExtraLarge,
          .accessoryCircular,
          .accessoryRectangular,
          .accessoryInline,
      ])
    }
}

WidgetView.swift

//
//  WidgetView.swift
//  RemindMe
//
//  Created by Charel Felten on 06/07/2021.
//

import SwiftUI
import WidgetKit

struct WidgetView: View {
  var entry: MyWidgetProvider.Entry
  
  @Environment(\.colorScheme) var colorScheme
  @Environment(\.widgetFamily) var widgetFamily
  
  var body: some View {
    switch widgetFamily {
    case .systemSmall, .systemMedium:
      SystemWidgetView(notes: Array(entry.notes.prefix(4)))
    case .systemLarge, .systemExtraLarge:
      SystemWidgetView(notes: Array(entry.notes.prefix(4)))
    default:
      Text(String(entry.notes.count))
    }
  }
}

struct SystemWidgetView: View {
  var notes: [Note]
  @Environment(\.colorScheme) var colorScheme
  
  var body: some View {
    ZStack {
      colorScheme == .dark ? Color.black : Color.white
      Rectangle()
        .fill(colorScheme == .dark ? Color.black : Color.white)
        .overlay {
          VStack(alignment: .leading, spacing: 5) {
            ForEach(notes, id: \.self) { note in
              Text(note.content!)
                .fontWeight(.medium)
                .font(.callout)
                .foregroundColor(note.getPrimaryColor())
                .frame(maxWidth:.infinity, maxHeight: .infinity, alignment: .leading)
                .padding(.vertical, 5)
                .padding(.horizontal, 10)
                .background(note.getWidgetBackgroundColor())
                .cornerRadius(5)
            }
          }
        }
        .cornerRadius(15)
        .padding(5)
    }
  }
}

struct WidgetView_Previews: PreviewProvider {
  // NOTE: if it crahes its this bug: https://www.appsloveworld.com/coding/xcode/66/fail-to-preview-widget-in-xcode-with-swiftui-preview
  // FIX: the membership of this class must
  // be only the widgeet target, no other target
  static var previews: some View {
    WidgetView(
      entry: MyWidgetEntry(
        date: Date(),
        notes: Note.previewNotes
      )
    )
    .previewContext(WidgetPreviewContext(family: .systemSmall))
  }
}

完整代码:

https://github.com/charelF/RemindMeApp/tree/ace8ed293bf1e7f3af673e16a1e3076c5edd64c8
最后,我想感谢@Owen Zhao和@Reinhold Männer。我很感激你们的回答,不幸的是,虽然他们没有解决我的问题,所以我不太愿意把奖金分配给你们中的任何一个。如果我能分的话,我会分的。谢谢你们的贡献!

相关问题