Widgets Not Refreshing Timelines

I'm making an app where there are two widgets. Both widgets are supposed to get their timelines once per day, as all data for the day is known at midnight. I'm having an issue where when put on a development device, the widgets' timelines work correctly, but do not refresh the next day. I've tried both .atEnd and .after(Date) refresh policies and neither seems to work. Does anyone know why the widget isn't refreshing properly? I'm almost certain that I'm under the daily limit of refreshes (one timeline refresh and ~12 timeline entries per day). Thank you for any help! Dev device iPhone 15 Pro on 17.5.1 (21F90) with Xcode Version 15.4 (15F31d). Below is the timeline code for one of the widgets:

struct EndTimeProvider: TimelineProvider {
    
    var currentHour: Int {
        Calendar.current.component(.hour, from: .now)
    }
    var currentMinute : Int {
        Calendar.current.component(.minute, from: .now)
    }
    var placeholderSixthPeriod: Period {
        var endMinute: Int = 0
        var endHour: Int = 0
        if currentMinute > 60-14 {
            endHour = currentHour + 1
            endMinute = (currentMinute + 14) % 60
        } else {
            endHour = currentHour
            endMinute = currentMinute + 14
        }
        return Period(name: "Period 6", start: "00:00", end: "\(endHour):\(endMinute)")
    }

    
    func placeholder(in context: Context) -> EndTimeEntry {
        return EndTimeEntry(date: .now, displayPeriod: placeholderSixthPeriod, scheduleName: "Regular Day")
    }

    func getSnapshot(in context: Context, completion: @escaping (EndTimeEntry) -> ()) {
        if context.isPreview {
            completion(placeholder(in: context))
            return
        }

        let entry = EndTimeEntry(date: .now, displayPeriod: placeholderSixthPeriod, scheduleName: "Regular Day")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [EndTimeEntry] = []
        
        let context = PersistenceController.shared.backgroundContext
        let scheduleFetch = StoredScheduleOnDate.fetchRequest()
        
        do {
            let storedSchedules = try context.fetch(scheduleFetch)
            
            let currentDate = Date()
            if let todaySchedule = storedSchedules.first(where: {
                Calendar.current.isDate($0.date!, equalTo: currentDate, toGranularity: .day)
            })?.schedule?.asDayType() {
                
                // Have an entry at midnight when schedules are needed
                let morningStart = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: .now)!
                let morningPeriod = Period(name: "Good morning", start: "00:00", end: todaySchedule.periods.first!.start)
                let morningEntry = EndTimeEntry(date: morningStart, displayPeriod: morningPeriod, scheduleName: todaySchedule.name)
                entries.append(morningEntry)
                
                // Passing periods should show the next full period's end time.
                // This means that an entry's date should be the past period's end, or the start in the first period's case.
                
                let firstPeriod = todaySchedule.periods.first!
                let firstPeriodEntry = EndTimeEntry(date: firstPeriod.getStartAsDate(), displayPeriod: firstPeriod, scheduleName: todaySchedule.name)
                entries.append(firstPeriodEntry)
                
                for index in 1..<todaySchedule.periods.count {
                    let entry = EndTimeEntry(date: todaySchedule.periods[index-1].getEndAsDate(), displayPeriod: todaySchedule.periods[index], scheduleName: todaySchedule.name)
                    entries.append(entry)
                }
                
                
                // Have an entry at the end of the day to have the start time of the next day shown
                if let tomorrowSchedule = storedSchedules.first(where: {
                    Calendar.current.isDate($0.date!, equalTo: Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!, toGranularity: .day)
                })?.schedule?.asDayType() {
                    // At EOD, show tomorrow's start
                    let endOfDay: Date = todaySchedule.periods.last!.getEndAsDate()
                    let overnightPeriod: Period = Period(name: "Good night", start: todaySchedule.periods.last!.end, end: "00:00")
                    let overnightEntry = EndTimeEntry(date: endOfDay, displayPeriod: overnightPeriod, scheduleName: tomorrowSchedule.name, overrideDisplayDate: tomorrowSchedule.periods.first!.getStartAsDate())
                    
                    entries.append(overnightEntry)
                }
            }
            
        } catch {
            fatalError("Could not fetch from Core Data for widget timeline. \(error)")
        }
        let tomorrowMorning = Calendar.current.date(bySettingHour: 0, minute: 1, second: 0, of: Calendar.current.date(byAdding: .day, value: 1, to: .now)!)!
        let timeline = Timeline(entries: entries, policy: .after(tomorrowMorning))
        completion(timeline)
    }
}

struct EndTimeEntry: TimelineEntry {
    let date: Date
    let displayPeriod: Period
    let scheduleName: String
    let overrideDisplayDate: Date?
    init(date: Date, displayPeriod: Period, scheduleName: String, overrideDisplayDate: Date) {
        self.date = date
        self.displayPeriod = displayPeriod
        self.scheduleName = scheduleName
        self.overrideDisplayDate = overrideDisplayDate
    }
    init(date: Date, displayPeriod: Period, scheduleName: String) {
        self.date = date
        self.displayPeriod = displayPeriod
        self.scheduleName = scheduleName
        self.overrideDisplayDate = nil
    }
}
...
Answered by diltondev in 796163022

For those wondering – the issue was not with the timeline code but rather more with the persistent stores I was using for Core Data. There seems to be an issue when background tasks try to access properties without inverses. When I was adding back inverses to migrate to CloudKit, the issue seemed to be resolved so as a general rule of thumb make sure that all your core data relationships have inverses or random things will break I guess.

I have similar issues. I can't get widgets to work properly at all.

I'm at the point I'm thinking of removing widgets from my app.

Sorry I can't help you, but you're not alone.

Accepted Answer

For those wondering – the issue was not with the timeline code but rather more with the persistent stores I was using for Core Data. There seems to be an issue when background tasks try to access properties without inverses. When I was adding back inverses to migrate to CloudKit, the issue seemed to be resolved so as a general rule of thumb make sure that all your core data relationships have inverses or random things will break I guess.

Widgets Not Refreshing Timelines
 
 
Q