Inconsistent Widget Behaviour Between Devices in iOS 14

Hello All,

I recently launched an iOS 14 Widget for my app. In testing everything seemed a-okay but on launch I am having inconsistent behaviour. On some devices the widget loads as just black, on other devices it only loads the placeholder content and on some devices it works as intended. The behaviour exhibited, good or bad, is consistent in the Add Widget Screen and when it's added to the Home Screen. On the devices where my widget does not work, other widgets do work.

The content in the Widget only changes when something is changed in the app thus I call WidgetCenter.shared.reloadAllTimelines() in the SceneDelegate when sceneWillResignActive is called. The expected behaviour is that when the user backgrounds the application the widget will update. On the devices that show black or placeholder content this Widget Center Update does not work, on the devices where it does work the update function works as expected.

This is the code for my Widget:

Code Block
  struct ThisWeekProvider: TimelineProvider {
    func placeholder(in context: Context) -> ThisWeekEntry {
      return ThisWeekEntry(date: Date(), thisWeekJSON: getDefaultTimeSummaryJSON())
    }
   
    func getSnapshot(in context: Context, completion: @escaping (ThisWeekEntry) -> ()) {
   
      var thisWeekData = TimeSummaryJSON()
      if context.isPreview {
        thisWeekData = getThisWeekData()
      } else {
        thisWeekData = getThisWeekData()
      }
   
      let entry = ThisWeekEntry(date: Date(), thisWeekJSON: thisWeekData)
      completion(entry)
    }
   
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
      let entries: [ThisWeekEntry] = [ThisWeekEntry(date: Date(), thisWeekJSON: getThisWeekData())]
   
      let timeline = Timeline(entries: entries, policy: .after(entries[0].thisWeekJSON.endDate))
      completion(timeline)
    }
  }
   
  struct ThisWeekEntry: TimelineEntry {
    let date: Date
    let thisWeekJSON: TimeSummaryJSON
  }
   
  struct ThisWeekWidgetEntryView : View {
    var entry: ThisWeekProvider.Entry
    var body: some View {
      COMMENT - Generate View
      COMMENT - Use data from 'entry' to fill widget
  }
   
  struct ThisWeekWidget: Widget {
    let kind: String = K.thisWeekWidget
   
    var body: some WidgetConfiguration {
      StaticConfiguration(kind: kind, provider: ThisWeekProvider()) { entry in
        ThisWeekWidgetEntryView(entry: entry)
      }
      .configurationDisplayName("this_week_widget".localized())
      .description("this_week_description".localized())
      .supportedFamilies([.systemSmall])
    }
  }


The custom data type 'TimeSummaryJSON' is as follows:

Code Block
  struct TimeSummaryJSON: Codable {
     
    var labourIncome: String = "$0.00"
    var equipmentIncome: String = "$0.00"
    var days: String = "0"
    var hours: String = "0"
     
    var endDate: Date = Date()
    var settingsFirstDayOfWeek: String = K.monday
    var localeFirstDayOfWeek: Bool = false
     
  }


The custom function that retrieves the data 'getThisWeekData()' is as follows:

Code Block
  private func getThisWeekData() -> TimeSummaryJSON {
    if let encodedData = UserDefaults(suiteName: AppGroup.shared.rawValue)!.object(forKey: K.thisWeek) as? Data {
      if let thisWeekJSON = try? JSONDecoder().decode(TimeSummaryJSON.self, from: encodedData) {
        return checkExpiryForCurrentWeek(thisWeekJSON)
      } else {
        print("Decoding Error - Return Default This Week")
        return getDefaultTimeSummaryJSON()
      }
    } else {
      print("No Shared Data - Return Default This Week")
      return getDefaultTimeSummaryJSON()
    }
  }


The process of saving and retrieving the data works like this:
  1. SceneDelegate calls sceneWillResignActive

  2. Data is pulled from the Local Realm Database, calculated and saved into a TimeSummaryJSON

  3. TimeSummaryJSON is encoded and saved to a shared AppGroup

  4. WidgetCenter.shared calls reloadAllTimelines()

  5. Widget decodes JSON data from AppGroup

  6. If the JSON Decode is successful the current user data is shown in the widget, if the JSON Decode fails a default TimeSummaryJSON is sent instead

I've looked over my code quite extensively and read countless forums and it seems that I am doing everything correctly. Have I missed something? Could anyone suggest why the behaviour is inconsistent between devices? I'm well and truly stuck and am not sure what to try next.

Any help you can offer would be kindly appreciated.

Thank you!
Hi,
I have similar problem.
I just published my application to TestFlight. I faced the problem, that widgets are not working properly on tested device - iPhone11 . They are in the list of widgets, but are empty - shadow places instead of real values or placeholder only.

On my phone and also in simulator everything works properly and XCode gives no error, also while exporting to AppConnect there is no error.

Finally I found, that when I change Archive scheme to debug instead of Release, it works as expected.
But this scheme should be Release for final version? I am also afraid what will happened when publish app to store.
I finally got to the bottom of my issue thanks to some help on reddit. The problem was an image that I was using in the widget was too large in resolution, thus large in file size and it capped out the 30MB Memory Limit of Widgets.

The reddit user that helped me had this to say:

'Black or redacted widget means that during loading you hit the 30 mb memory limit.
Also if you're reloading more than one widget at a time, it seems that there's a higher chance you'll hit the memory budget.'

'Forgot to mention that black/redacted widget can also happen if the widget crashes (not necessary due to memory budget). So maybe you have some threading problems or are force unwrapping something that is nil.'
Inconsistent Widget Behaviour Between Devices in iOS 14
 
 
Q