问题描述
我在主应用中使用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)
}
}