Reputation: 1036
I'm currently developing an application using SwiftUI.
I want to reload data from CoreData
in a widget
to update newer data every when I close a host App.
But in my codes, the data doesn't get reloaded...
How could I solve that?
TimerApp.swift (Host APP)
import SwiftUI
import WidgetKit
@main
struct TimerApp: App {
@Environment(\.scenePhase) private var scenePhase
let persistenceController = PersistenceController.shared.managedObjectContext
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController)
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .inactive {
WidgetCenter.shared.reloadAllTimelines()
}
}
}
}
}
TimerWidget.swift(Widget Extension)
import WidgetKit
import SwiftUI
import CoreData
struct Provider: TimelineProvider {
var moc = PersistenceController.shared.managedObjectContext
var timerEntity:TimerEntity?
init(context : NSManagedObjectContext) {
self.moc = context
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
}
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), timerEntity: timerEntity!)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), timerEntity: timerEntity!)
return completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, timerEntity: timerEntity!)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let timerEntity:TimerEntity
}
struct TimerWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
return (
VStack{
Text(entry.timerEntity.task ?? "")
}
)
}
}
@main
struct TimerWidget: Widget {
let kind: String = "TimerWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(context: PersistenceController.shared.managedObjectContext)) { entry in
TimerWidgetEntryView(entry: entry)
.environment(\.managedObjectContext, PersistenceController.shared.managedObjectContext)
}
}
}
UPDATED
I updated my code to execute NSFetchRequest
in the getTimeline
function as well as below.
But in my code, when I build this project I have an error like below:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TimerEntity task]: unrecognized selector sent to instance 0x600003175400' terminating with uncaught exception of type NSException
struct Provider: TimelineProvider {
var moc = PersistenceController.shared.managedObjectContext
@State var timerEntity:TimerEntity?
init(context : NSManagedObjectContext) {
self.moc = context
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
}
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), timerEntity: timerEntity ?? TimerEntity())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), timerEntity: timerEntity!)
return completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, timerEntity: timerEntity ?? TimerEntity())
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
Xcode: Version 12.0.1
iOS: 14.0
Life Cycle: SwiftUI App
Upvotes: 1
Views: 1373
Reputation: 54426
The code responsible for fetching data is only in init
:
struct Provider: TimelineProvider {
var moc = PersistenceController.shared.managedObjectContext
var timerEntity:TimerEntity?
init(context : NSManagedObjectContext) {
self.moc = context
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
}
...
}
It is run only when the Provider
is initialised. You need to execute this NSFetchRequest
in the getTimeline
function as well, so your timerEntity
is updated.
Also, if you execute WidgetCenter.shared.reloadAllTimelines()
every time scenePhase
== .inactive
it might be too often and your Widget might stop getting updated.
inactive
is called whenever your app is moved from the background to the foreground (in both directions).
Try using background
instead - this will be called only when your app is minimised or killed:
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .background {
WidgetCenter.shared.reloadAllTimelines()
}
}
Note: I'd recommend adding some safeguards around WidgetCenter.shared.reloadAllTimelines()
to make sure to not refresh it too often.
Upvotes: 1