Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to refresh Widget data?

My Code fetches two JSON variables and should show them on my Widget. The Widget stays blank. Without widget it shows me everything correct in my application.

What am I doing wrong? The API in the code is only for testing so you can also check that. Is there something I need to change to make it show in the widget?

My Struct:

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
}

Fetching JSON:

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()
        }
    }
}

Showing Widget:

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 should be shown as text but as I said it is blank.

like image 673
submariner Avatar asked Sep 20 '20 07:09

submariner


People also ask

How do I refresh my widgets?

Tap On The Widget Sometimes all you need to do to update the information display on a widget is to tap on it and open the app. When the app opens, its content will refresh, which should refresh the widget too.

How do I refresh widgets on Iphone?

We know this is not an ideal solution but it's a nifty little trick to refresh the widget. You need to open the app from the widget for a minute and close it. The widget will fetch new information from the app.

How often do Apple widgets refresh?

For a widget the user frequently views, a daily budget typically includes from 40 to 70 refreshes. This rate roughly translates to widget reloads every 15 to 60 minutes, but it's common for these intervals to vary due to the many factors involved. The system takes a few days to learn the user's behavior.

Do widgets use up data?

Widgets are extensions for an application, and they do not exist individually, therefore it constantly needs to fetch information from the application and refresh this information all the time to provide the user with up to date data.


1 Answers

You can't use the ObservedObject like you'd normally use in your App.

In Widgets you use a TimelineProvider which creates an Entry for your view.


  1. Add another property to your TimelineEntry, let's call it clubName:
struct SimpleEntry: TimelineEntry {
    let date: Date
    let clubName: String
}
  1. Update the NetworkManager and return results in the completion:
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()
    }
}
  1. Use the NetworkManager in the TimelineProvider and create timelines entries when the fetchData completes:
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)
        }
    }
}
  1. Use entry.clubName in the view body:
struct WidgetNeuEntryView: View {
    var entry: Provider.Entry

    var body: some View {
        VStack {
            Text(entry.date, style: .time)
            Text("Club: \(entry.clubName)")
        }
    }
}

Note that in the above example the reload policy is set to never to only load the data once.

You can easily change it to atEnd or after(date:) if you want to reload the timeline automatically.

If you need to reload the timeline manually at any point you can just call:

WidgetCenter.shared.reloadAllTimelines()

This will work in both App and Widget.


Here is a GitHub repository with different Widget examples including the Network Widget.

like image 175
pawello2222 Avatar answered Sep 22 '22 20:09

pawello2222