Detecting iOS Widgets in use for logging

Jaeho Yoo
4 min readJan 7, 2024

In my previous article, I show you how to optimize network calls for widgets.

Challenge — which widget is the most popular?

After minimizing network calls in my widgets, the server team was satisfied, and I felt good about solving the problem technically. 😄

However, now our PM wants to know which widget is the most popular. Yikes! I searched for an API to detect the widget’s lifecycle but found none.

Fortunately, the article ‘Lessons From Building iOS Widgets’ on Shopify Engineering was a big help. Thank you guys!

To determine the addition or removal of a Widget, the required API is WidgetCenter’s getCurrentConfigurations(_:) method.

my savior!

Using this method, you can retrieve [WidgetInfo]. Inside the WidgetInfo type, there’s a widgetFamily property we need. This means you can detect all the WidgetFamilies in use.

From Shopify’s article in the [Detecting, Adding, and Removing Widgets] section, it is evident that the getTimeline method calls getCurrentConfigurations.

Shopify’s widget detecting flow

In the section marked with a red under line, it states that widget removals are not immediately reflected; they’re only detected after several getTimeline calls.

In my testing, I found that widget removals are only detected upon the next getTimeline call. This issue was more annoying with the Lock Screen Widget (iOS 16+). Accessing just the Widget Gallery can trigger multiple getTimeline calls, risking false detection of widget additions.

Lock Screen Widget Gallery immediately triggers `getTimeline` multiple times.

If you add three different types (small, medium, large) of widgets to your Home screen and then access the Lock Screen Widget Gallery, you’ll observe an unexpectedly high number of getTimeline calls, way beyond five.

I’ve been grappling with widgets for months, but there are still mysteries to unravel. Moreover, logging through the logEvent method of Firebase Analytics doesn’t work in Widget Extensions. đŸ€Ż

WidgetDetector — for reliable logging

My requirements for the ‘widgets in use’ logging:

  • Logging doesn’t need to be immediate upon adding/removing a widget, but it must be accurate.
  • Since Firebase Analytics doesn’t work in Widget Extensions, logging must be done in the hosting app.
  • I want to know the types of WidgetFamily in use by users.

I found a solution that meets these requirements and want to share it with developers struggling with widgets. Unlike Shopify, I chose not to call getCurrentConfigurations in the getTimeline method.

My trick is — when the hosting app enters the foreground, call getCurrentConfigurations to retrieve current [WidgetInfo] and compare it with the old [WidgetInfo] stored in UserDefaults.

WidgetDetector flow

Let me show you WidgetDetector code. It’s very simple.

WidgetDetector code

Looking at the code, you’ll notice I store [WidgetFamily] in UserDefaults instead of a Singleton property. The reason is that a Singleton property acts as a short-lived cache.

Therefore, if a user updates the app from the App Store while some widgets are in use, the cache in the Singleton will reset and cause duplicate widget add logging.

Storing [WidgetFamily] in UserDefaults ensures data persistence unless the app is entirely deleted, safeguarding the data from app updates.

In the code, I access UserDefaults using KeyPath. For more details, feel free to check out my repository.

activate `detect()` when your App enter the foreground

It only takes a single line of code. I’ll call detect() when the hosting app enters the foreground. This way, we can log the addition or removal of widgets whenever the user launches the app.

Let’s look at how it works in a gif. First, I’ll add a small widget.

detecting small widget was added

When a small widget is added and the app enters the foreground, you can see that WidgetDetector detects it.

I also tested adding/removing lock screen widgets, but creating a gif for this was too heavy, so I made a video and uploaded it to YouTube. Please refer to the video below.

Now, you can remove print() and call logEvent() as you see fit. Whew, I think our PM will be happy now!

Full source code đŸ‘‡đŸ»

--

--