重新加载Widgetcenter的所有时间轴

问题描述

我在主应用中使用WidgetCenter.shared.reloadAllTimelines()刷新小部件。

该小部件包含一张图片一个通过json请求获取的字符串。 如果我使用上面的代码图片将立即刷新。这样就应该如此。

但是字符串保持不变。它需要执行另一个json请求。但事实并非如此。它显示了旧的String。为什么? 字符串通过TimelineEntry导入。所以我想我还需要重新加载TimelineEntry?

我该怎么做?对于我在视图中使用的字符串entry.clubname

这是一些示例代码。我删除了一些代码,以免使用太多代码

我的网络:

class NetworkManager: ObservableObject {
@Published var clubNameHome = "..."
init() {
            fetchData() // fetch data must be called at least once
        }
func fetchData() {
if let url = URL(string: "...) {
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { (gettingInfo,response,error) in
                if error == nil {
                    let decoder = JSONDecoder()
                    if let safeData = gettingInfo {
                        do {
                            let results = try decoder.decode(Results.self,from: safeData)
                            dispatchQueue.main.async {
                                 
                                self.clubNameHome = results.data[0]....
                              
                                if #available(iOS 14.0,*) {
                                    WidgetCenter.shared.reloadAllTimelines()
                                } else {
                                    // Fallback on earlier versions
                                }
                            }
                        } catch {
                            print(error)
                        }
                    }
                }
            }
            task.resume()
        }
    }
}

还有带有View的TimelineProvider:

struct Provider: IntentTimelineProvider {
    let networkManager = NetworkManager()
    
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(),configuration: ConfigurationIntent(),clubnamehome: networkManager.clubNameHome)
    }
    
    func getSnapshot(for configuration: ConfigurationIntent,in context: Context,completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(),configuration: configuration,clubnamehome: networkManager.clubNameHome)
        completion(entry)
    }
    
    func getTimeline(for configuration: ConfigurationIntent,completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        
        // Generate a timeline consisting of five entries an hour apart,starting from the current date.
        let currentDate = Date()
        
        let entryDate = Calendar.current.date(byAdding: .minute,value: 15,to: currentDate)!
        let entry = SimpleEntry(date: entryDate,clubnamehome: networkManager.clubNameHome)
        entries.append(entry)
        
        let timeline = Timeline(entries: entries,policy: .atEnd)
        completion(timeline)
    }
}

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

struct MyTeamWidgetEntryView : View {
    var entry: Provider.Entry
    var body: some View {
        
        HStack {
            Spacer()
            vstack (alignment: .leading,spacing: 0) {
                Spacer().frame(height: 10)
                HStack {
                    Spacer()
                    switch logo {
                    case "arminia":
                        Image("bundesliga1/arminia").resizable().aspectRatio(contentMode: .fit).frame(width: 90,height: 90,alignment: .center)
                    case "augsburg":
                        Image("bundesliga1/augsburg").resizable().aspectRatio(contentMode: .fit).frame(width: 90,alignment: .center)
                    default:
                        Image("bundesliga1/bayern").resizable().aspectRatio(contentMode: .fit).frame(width: 90,alignment: .center)
                    }
                }
                HStack{
                    Text(entry.clubname)
                }
            }
        }}}

解决方法

问题是您的网络呼叫应该在getTimeline(for:in:completion:) -> Void)中。它将在WidgetViewModel类的完成处理程序中执行您的数据。 如代码所示,getTimeline方法将每15分钟自动触发一次。

因此它必须寻找类似的东西,但请注意,您必须根据您的项目更改代码。而且由于它仅用于窗口小部件,所以我建议您使用Combine并仅为该窗口小部件创建此网络调用。

所以我将以这种方式重写您的NetworkManager:

import Combine

class NetworkManager<Resource> where Resource: Codable {

  func fetchData() -> AnyPublisher<Resource,Error> {

    URLSession.shared
      .dataTaskPublisher(for: URL(string: "....")!) // Don't forget your url
      .receive(on: DispatchQueue.main)
      .map(\.data)
      .decode(
        type: Resource.self,decoder: JSONDecoder())
      .mapError { error -> Error in
        return error }
      .eraseToAnyPublisher()
  }
}

然后,我将创建一个视图模型来处理小部件逻辑,并设置一个方法来处理NetworkManager调用以获取数据:

import Combine

final class WidgetViewModel {

  private var subscriptions = Set<AnyCancellable>()

  // You can see here how to use a completion handler that is set to be
  // a String,the same as the value your expect for 'clubnamehome'.

  func downloadWidgetData(completionHandlers: @escaping (String) -> Void) {
    NetworkManager<String>().fetchData()
      .sink(
        receiveCompletion: { _ in },receiveValue: { data in
          completionHandlers(data) }) // Completion Handler set here
      .store(in: &subscriptions)
  }
}

现在,在您的struct Provider: IntentTimelineProvider中,将getTimeline更改为使用viewModel中的NetworkManager。您可能需要对代码进行一些更改,因为我没有您的网址来测试它以及Widget的完整代码,但是它应与此设置紧密配合。

let viewModel = WidgetViewModel()

func getTimeline(for configuration: ConfigurationIntent,in context: Context,completion: @escaping (Timeline<SimpleEntry>) -> Void) {

  let currentDate = Date()
  guard let refreshTimeline = Calendar.current.date(byAdding: .minute,value: 15,to: currentDate) else { return }

  // You must declare your network call here and handle the data as a completionHandler.
  // Like this,the widget will refresh every 15 minutes.

  viewModel.downloadWidgetData { data in  // CompletionHandler is used like this.
    let entry = SimpleEntry(date: currentDate,configuration: configuration,clubnamehome: data)
    let timeline = Timeline(entries: [entry],policy: .after(refreshTimeline))
    completion(timeline)
  }
}