WidgetKit complications won't update

We are migrating ClockKit complications to WidgetKit in our watch app (watchOS 9+). The migration went smoothly, UI part works just fine. However, we've hit the wall with widgets not updating when requested by the watch app. I believe we are missing something very simple and fundamental, but couldn't find what exactly so far. Advice and tips would be very welcome! 🙇‍♂️

Our implementation details:

  • Whenever data is changed in the main app, the updated data is submitted to the watch app via WatchConnectivity framework using WCSession.default.transferCurrentComplicationUserInfo(_:). According to documentation, this method should be used to transfer complication-related data, since it will wake the watch app even if it is in the background or not opened at all.
  • Watch app receives updated data and stores it in UserDefaults shared with Watch Widget Extension hosting WidgetKit complications via App Group.
  • Watch app then request widget timeline reload via WidgetCenter.shared.reloadAllTimelines(). According to documentation, it reloads the timelines for all configured widgets belonging to the containing app, so it seems the appropriate way to reload WidgetKit complications.
  • Widget Timeline Provider class in Watch Widget Extension reads updated data from shared UserDefaults and uses it to provide the updated snapshot for widget views to render.

We believe our implementation logic is correct, but it doesn't work, for some reason. Widgets sometimes update when the watch app is opened, but not always. The most definitive way to force widgets to update is to switch to a different watch face, which confirms that the Widget Timeline Provider has access to properly updated data.

P.S. We are aware of the daily reload budget imposed on widgets, so we use widgets reload trigger sparingly. Anyway, according to documentation, reload budget is not effective when in DEBUG mode, but widgets won't reload even in DEBUG mode.

Thank you!

Post not yet marked as solved Up vote post of Serzhas Down vote post of Serzhas
3.1k views

Replies

Same issue here. Posted this thread https://developer.apple.com/forums/thread/735714

Did you figure it out?

I've opened a Feedback Request for this issue: FB12926788. It is currently being investigated by Apple. I will post any updates here.

P.S. I've created a sample project to illustrate the issue with WidgetKit complications.

@Serzhas any response from Apple?

Any update from Apple?

Apple responded that this appears to be a bug on their side, but no ETA for a fix was given. We are keeping our fingers crossed 🤞🤞

Has anyone solved this problem? Can anyone update the widget using a different method?

I have experienced the same. widgets on watchOS won't use latest data in userDefaults. Even though the AW's app itself uses the latest data in userDefaults.

However, from my experience, if I wait for hours (but not more than 24 hours.) the widget uses correct latest data from userDefaults. And subsequent widget rendering will also use latest data in userDefaults.

Can anyone confirm that if you wait for says 4-5 hours or overnight, the widget will read correct data?

My recent research yielded some results.

We've been updating our Watch app, which is WatchKit based, to support watchOS 10 features. We've added new SwiftUI scenes to it, and the app itself works like a charm - no issues whatsoever. However, it appears that WatchKit apps have issues with WidgetKit complications - they won't update (or will have a significant delay) even when the app is in the foreground (such updates do not count towards the daily update quota).

However, this issue is not observed if the Watch app is pure SwiftUI - widgets will update immediately, including Smart Stack widget. Therefore, we are rewriting our Watch app to SwiftUI and getting rid of ClockKit completely (doing this forced us to increase the minimum watchOS version to 9.0 🤷‍♂️).

Hope that will be helpful to someone. 🤓

P.S. All said above doesn't solve the issue with data communication not happening between iPhone and Watch if the Watch app is in the background.

  • Thanks for the insight. Can you tell whether the widgets on watchOS always use the latest data in userDefaults?

    In my case, I notice that the widgets doesn't always use the latest data even though the app itself uses the latest data (can confirm that watch app receive latest userDefaults from iPhone). It is like after some times (sometimes in minutes sometimes in hours), the widgets will start using latest data in userDefaults.

  • Not sure about your project, but in my case problem wasn't stale data in UserDefaults (App Group), but rather the fact, that widget TimelineProvider is not called to update widget data. I never experienced stale data during development or testing, so I highly double that you have that in your project 🤓

  • BTW, if you attach a debugger to your Widget Extension process it will update properly. Try it, and you will quickly validate whether stale data is your issue or not. Run your app on simulator, add your widget to Home (or Lock) screen, and then go to Debug -> Attach To Process and select your Widget Extension process (it should be listed on top at the "Likely Targets" section). https://mastodon.social/@brianmueller/111026274124701811

I ran into this same issue as well. If you update your project to migrate to a single WatchKit target, you should be able to force immediate updates again. Be warned though, the migration introduced a bunch of build errors into my project that took a couple hours for me to fix, and there are other bugs that you will run into that are specific to a single target watch app: ClockKit complications will break and need to be reinstalled, and location updates will fail if you delete then reinstall your watch app. Quite a mess.

  • Thank you for figuring this out. I've had the exact same issue as the topic starter, and migrating to a single WatchKit target fixed it! Just like in your case, migration wasn't exactly smooth, but still much better than having to rework the whole app into SwiftUI. I can confirm that my ClockKit complications are broken too now, and I'm trying to figure out what's going on there. I can add them to my watch faces just fine, but they never update and just keep showing the preview name.

  • OK, I figured it out. the key to getting CLKComplications working on a single target watch app is to implement the getComplicationDescriptors(handler:) method in your ComplicationController class. I had the Complication Families specified in the info.plist file which doesn't work in watchOS 7, and that key must be deleted from info.plist once the new method is added.

Add a Comment

Hi everyone,

I'm having the same issue. What I found is that if the Watch App is open, or was recently put in background (less than a minute approx), transferCurrentComplicationUserInfo works fine. Otherwise, Complications are never updated, BUT once I open the Watch App again, ALL calls previously made to transferCurrentComplicationUserInfo are sequentially received in func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]), and therefore Complications are updated. So the issue (at least in my case) seems to be that transferCurrentComplicationUserInfo doesn't wake up ExtensionDelegate.

Is there anything new from Apple, or anyone who has managed to fix it without migrating to the SwiftUI lifecycle?

Extra data:

  • I made sure that the quota of 50 is not exhausted when I test.
  • This is the ExtensionDelegate (summarized):
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {
	let session = WCSession.default

	override init() {
		super.init()
	    if WCSession.isSupported() {
	      session.delegate = self
	      session.activate()
	    }
	}

 	func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
    	WatchWidgetSessionHandler.shared.processComplicationUserInfo(userInfo)
  	}
} 
  • This is the code from the iOS side(summarized):
class WatchAppDataManager: NSObject, WCSessionDelegate {
   override init() {
    super.init()
     let session = WCSession.default
     session.delegate = self
     session.activate()
   }

  func sendDataToWidget(for kinds: [WatchWidgetKind]) async {
    guard
      WCSession.default.activationState == .activated,
      WCSession.default.isComplicationEnabled
    else {
      return
    }

    let widgetsData = dataProvider.getData(for: kinds)
    if !widgetsData.isEmpty {
      WCSession.default.transferCurrentComplicationUserInfo(widgetsData)
    }
  }
}
  • In addition, what I'm experiencing is that transferCurrentComplicationUserInfo(_:) behaves exactly as transferUserInfo(_:) is described in the documentation.

    I'm testing on WatchOS 10.2 and iOS 17.12

  • Same problem here... and similar to Szerhas's comment on an earlier post, I'm seeing if my watchOS app - on the watch on my wrist - is running in the debugger, the complications run perfectly and promptly. If I detach from the debugger, they stop updating.

Add a Comment