Widget & Snapshot Blank on iOS 26

Some of the users of my app reported that, the widgets cannot be loaded, event after restarts and re-installs. It seems that it is not rare, I have tens of reports on this (and probably much more who didn't report).

It seems the widget is blank even on the gallery screen while you are first adding it:

struct SingleWidgetProvider: AppIntentTimelineProvider {
    @Environment(\.widgetFamily) var family
    private let viewModel: WidgetViewModel = WidgetViewModel()
    
    func placeholder(in context: Context) -> SingleEntry {
        SingleEntry(date: Date(), habit: .singleSample)
    }

    func snapshot(for configuration: SingleWidgetConfigurationIntent, in context: Context) async -> SingleEntry {
        guard let habit = Array(PersistenceManager.shared.retrieveHabitList().habits).first else {
            return SingleEntry(date: Date(), habit: .singleSample)
        }
        
        let displayable = viewModel.displayableFromHabit(habit, separateComponents: true, secondOffset: 0)

        return SingleEntry(date: Date(), habit: displayable)
    }
    
    func timeline(for configuration: SingleWidgetConfigurationIntent, in context: Context) async -> Timeline<SingleEntry> {
        var entries: [SingleEntry] = []

        guard let counter = configuration.currentCounter else {
            return Timeline(entries: [SingleEntry(date: Date(), habit: .singleSample)], policy: .atEnd)
        }
        
        guard let habit = PersistenceManager.shared.retrieveHabit(habitKey: counter.id) else {
            return Timeline(entries: [SingleEntry(date: Date(), habit: .singleSample)], policy: .atEnd)
        }
        
        let currentDate = Date()
        for secondOffset in 0 ..< 100 {
            let displayable = viewModel.displayableFromHabit(
                habit,
                separateComponents: true,
                secondOffset: secondOffset,
                symbolName: habit.habitSymbol?.name ?? "",
                overrideColor: configuration.currentColor.color, 
                overrideButtonVisibility: configuration.currentButtonVisibility,
                overrideDisplayOption: configuration.currentDisplayOption
            )
            
            let entryDate = Calendar.current.date(byAdding: .second,
                                                  value: secondOffset,
                                                  to: currentDate)!
            let entry = SingleEntry(date: entryDate, habit: displayable)
            entries.append(entry)
        }

        return Timeline(entries: entries, policy: .atEnd)
    }
}

struct SingleEntry: TimelineEntry {
    let date: Date
    let habit: HabitDisplayable
}

static var singleSample: HabitDisplayable {
        return HabitDisplayable(habitKey: nil,
                                title: LS("sampleWidgetHabitTitle"),
                                dates: [Date(timeIntervalSince1970: 1507158360)],
                                displayOption: .dayMonthYear,
                                secondOffset: 0,
                                separateComponents: true)
    }


func displayableFromHabit(
        _ habit: Habit,
        separateComponents: Bool,
        secondOffset: Int,
        symbolName: String = "",
        overrideColor: Color? = nil,
        overrideButtonVisibility: WidgetButtonVisibility? = nil,
        overrideDisplayOption: WidgetDisplayOption? = nil
    ) -> HabitDisplayable {
        let latestDates: [HabitDate]
        let displayOption: DisplayOption
        if let overrideDisplayOption {
            if overrideDisplayOption == .sameAsCounter {
                displayOption = habit.settings?.toValue.displayOption ?? .dayMonthYear
            } else {
                displayOption = DisplayOption(rawValue: overrideDisplayOption.rawValue - 1) ?? .dayMonthYear
            }
        } else {
            displayOption = habit.settings?.toValue.displayOption ?? .dayMonthYear
        }
        let displayMode = displayOption.mode
        
        
        switch displayMode {
        case .timePassed, .date:
            latestDates = PersistenceManager.shared.latestDate(habit: habit).map { [$0] } ?? []
        case .activity:
            latestDates = PersistenceManager.shared.retrieveHabitDates(habit: habit, limitDate: displayOption.limitDate())
        }
        
        let displayButton: Bool
        if let overrideButtonVisibility {
            if overrideButtonVisibility == .sameAsCounter {
                displayButton = habit.settings?.toValue.buttonType == .onRow
            } else {
                displayButton = overrideButtonVisibility == .show
            }
        } else {
            displayButton = false
        }
        
        return HabitDisplayable(
            habitKey: habit.habitKey,
            title: habit.title,
            dates: latestDates.map { $0.date },
            displayOption: displayOption,
            secondOffset: secondOffset,
            separateComponents: separateComponents,
            color: overrideColor ?? habit.habitColor?.color ?? .appPrimary,
            symbolName: symbolName,
            displayButton: displayButton
        )
    }

I provided a large portion of my code, let me know if you need more. The strange thing here is, even if the DB connection is broken somehow, it should have shown the default option (singleSample).

I am not able to reproduce/fix this for months now, so any help is very appreciated.

Thank you for the post and the code snippet. Could you please provide a focused sample that is easier to download and execute on my computer to identify any potential issues?

If so, please share a link to your test project. That'll help us better understand what's going on. If you're not familiar with preparing a test project, take a look at Creating a test project.

I am interested how only a few users experience this, do you have a default view? If your widget relies on network data, ensure that network connectivity is not an intermittent issue for these users.

Resource: https://developer.apple.com/documentation/widgetkit

Looking forward to download your code and see how I can reproduce it.

Albert Pascual
  Worldwide Developer Relations.

Widget & Snapshot Blank on iOS 26
 
 
Q