Wie aktualisiere ich Widget-Daten?
Mein Code ruft zwei JSONVariablen ab und sollte sie in meinem Widget anzeigen. Das Widget bleibt leer. Ohne Widget zeigt es mir alles richtig in meiner Anwendung.
Was mache ich falsch? Die API im Code dient nur zum Testen, sodass Sie dies auch überprüfen können. Muss ich etwas ändern, damit es im Widget angezeigt wird?
Meine Struktur:
import Foundation
struct Results: Decodable {
let data: [Post]
}
struct Post: Decodable, Identifiable {
let id: String
var objectID: String {
return id
}
let home_name: String
let away_name: String
}
JSON abrufen:
import Foundation
class NetworkManager: ObservableObject {
@Published var posts = [Post]()
@Published var test = ""
@Published var test2 = ""
func fetchData() {
if let url = URL(string: "https://livescore-api.com/api-client/teams/matches.json?number=10&team_id=19&key=I2zBIRH3S01Kf0At&secret=6kLvfRivnqeNKUzsW84F0LISMJC1KdvQ&number=7&team_id=46") {
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.posts = results.data
self.test = results.data[0].away_name
self.test2 = results.data[0].home_name
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
Widget anzeigen:
import WidgetKit
import SwiftUI
import Intents
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, 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()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
struct WidgetNeuEntryView : View {
@ObservedObject var networkManager = NetworkManager()
var entry: Provider.Entry
var body: some View {
Text(networkManager.test)
}
}
@main
struct WidgetNeu: Widget {
let kind: String = "WidgetNeu"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
WidgetNeuEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct WidgetNeu_Previews: PreviewProvider {
static var previews: some View {
WidgetNeuEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
networkManager.test sollte als Text angezeigt werden, aber wie gesagt ist es leer.
Antworten
Sie können nicht das verwenden, was ObservedObjectSie normalerweise in Ihrer App verwenden würden.
In Widgets verwenden Sie ein TimelineProvider, das ein Entryfür Ihre Ansicht erstellt.
- Fügen Sie Ihrer Eigenschaft eine weitere Eigenschaft hinzu
TimelineEntry, nennen wir sieclubName:
struct SimpleEntry: TimelineEntry {
let date: Date
let clubName: String
}
- Aktualisieren Sie die
NetworkManagerund geben Sie die Ergebnisse zurück incompletion:
class NetworkManager {
func fetchData(completion: @escaping ([Post]) -> Void) {
...
URLSession(configuration: .default).dataTask(with: url) { data, _, error in
...
let result = try JSONDecoder().decode(Results.self, from: data)
completion(result.data)
...
}
.resume()
}
}
- Verwenden Sie die Einträge
NetworkManagerinTimelineProviderund erstellen Sie Zeitleisteneinträge, wenn der VorgangfetchDataabgeschlossen ist:
struct Provider: TimelineProvider {
var networkManager = NetworkManager()
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), clubName: "Club name")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
let entry = SimpleEntry(date: Date(), clubName: "Club name")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
networkManager.fetchData { posts in
let entries = [
SimpleEntry(date: Date(), clubName: posts[0].home_name)
]
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}
}
}
- Verwendung
entry.clubNameim Ansichtskörper:
struct WidgetNeuEntryView: View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.date, style: .time)
Text("Club: \(entry.clubName)")
}
}
}
Beachten Sie, dass im obigen Beispiel die Richtlinie zum erneuten Laden so festgelegt ist, dass neverdie Daten nur einmal geladen werden.
Sie können es einfach ändern atEndoder after(date:)wenn Sie die Timeline automatisch neu laden möchten.
Wenn Sie die Timeline zu einem beliebigen Zeitpunkt manuell neu laden müssen, können Sie einfach Folgendes aufrufen:
WidgetCenter.shared.reloadAllTimelines()
Dies funktioniert sowohl in der App als auch im Widget.
Hier ist ein GitHub-Repository mit verschiedenen Widget-Beispielen, einschließlich des Netzwerk-Widgets.