Widgets in iOS 14 — features and restrictions

Anton Davydov
Cardsmobile
Published in
11 min readMar 16, 2021

Last Year Apple released a number of interesting tools for iOS developers that can rapidly improve user experience. One of these features is new widgets. While we waited for a new feature to come out, I’d like to share our experience on developing a widget for “The Wallet’’ app with you and explain what features and restrictions our team has encountered on the beta versions of Xcode that we used for developing the widgets back then between summer and fall 2020.

Let’s start with a definition: Widget is a feature that shows actual information without actually launching the application itself and is always at hand for an average user. One can already use it starting from iOS 8 (Today Extension), but, in my own experience, it’s not something that I usually happen to check: it has a dedicated workspace on a desktop that I personally rarely open.

As we can see, iOS 14 brought us the reincarnation of the widgets deeper integrated into the iOS ecosystem and easier to use (in theory).

Working with the loyalty cards is one of the major functions of “The Wallet” app. Now and then there are some requests that appear in the App Store comments to implement adding a widget into “Today”. Users would want to deal faster with the card and get their discount standing at the checkout as every second of delay would cause these reproachful stares of the whole line behind. In this case a widget would spare some precious seconds of finding the card in the app and making the payment process even faster. Stores would be also grateful too: smaller lines + faster checkout = better service.

This year Apple suddenly released the new iOS right after the WWDC20 giving the developers just one day to finish the changes to their apps on Xcode GM, but, hopefully, we happened to be ready for the release since our team has started creating on the early beta of Xcode. The iOS update goes real quick according to the statistics; users most likely will go on to check which widgets their device already has by now.

Further on we would want to add more actual data: e.g. balance, barcode, last unreads from partners and other notifications (e.g. a notification to confirm and/or activate a card). At the moment the result looks as follows:

Adding a widget into the project

Same as other additional tools a widget is added as an extension to the main project. After added, the Xcode generates the code for a widget and for other main classes. Here we faced the first interesting feature: for our project the code did not compile properly because in one file it automatically added a prefix for class names (ye-ye, these exact Obj-C prefixes), while for generated files it didn’t. As the saying goes,not god but man makes pot and pan and, perhaps, different teams inside of Apple didn’t agree among themselves. In order to set up the prefix for your project, you are required to fill the Class Prefix field in the File Inspector of the main target of the app.

For those who follow the WWDC novelties it ain’t a secret that Widgets can only be made using SwiftUI. That is the way Apple force-pushes the updates for their technologies: even if the whole app is made with UIKit, please, be so kind to use SwiftUI. On the other hand, it’s a great opportunity to use a new framework for developing a new feature; in this particular case it’s greatly integrated into the process: no need for status changes, no extra navigation — you only need to declare a static UI. This means along with the new framework you also gain new restrictions, since old widgets in Today may contain way more logic and animations.

One of the main innovations of SwiftUI is the ability to see the preview without actually launching on the emulator or a device. It’s truly a cool one, but, sadly, for bigger projects (our contains 400k + code lines) it works kinda slow even on a brand new Macbook — it’s way faster to launch it on a device. The alternative for that is having an empty project or a playground for a rapid prototyping.

A debug function is present with a dedicated Xcode scheme. Debugging on an emulator acts unstable even on Xcode 12 beta 6 version, so it is worth sacrificing a test device for that purpose, update it to iOS 14 and test on it. Be prepared that this part may not operate properly even for the release versions.

Interface

A user is suggested to choose from widget types(WidgetFamily) of three sizes: small, medium or large.

In order to add the widget you have to declare supported ones:

Our team has decided to stand for small and medium — to show one favorite card for a smaller one or 4 for medium one. Adding a widget to the desktop goes from Control Centrer — there user has to select the desired view:

“Add widget” button can be customized using Assets.xcassets -> AccentColor, as well as the widget name and description (see the code sample above).

If you happen to hit the view types limit, it can be expanded via the WidgetBundle:

Since a widget shows a fingerprint of some status, the only option for interaction is to open an app itself upon touching some element or the whole widget. No animation, navigation or other view. However, you can put a deep link to the main app. By that for a small widget the touchable area is the whole frame — for that we use widgetURL(_:) method. For medium and big view tap is available and for that we use Link structure of SwiftUI.

The final view of 2 widgets of different scales looks as follows:

The following rules and requirements may help you developing an interface in accordance with Apple guidelines:

  1. Focus the widget on a single problem or function — do not try to cover the functionality of the whole app;
  2. Give more information depending on the widget size — do not scale the content only;
  3. Show dynamic data that may vary or change during the day. Constantly changing or fully static information is not something welcomed;
  4. Widget must provide users with the actual information and is not supposed to serve as another way to open an app;

Ok, so we’re done with the view and the next step is to decide, which cards to choose and how to show them to the user. There can be more than 4. Let’s see some examples:

  1. Give an option to select cards upon your own discretion. Nobody else but him/her knows exactly what cards would be needed;
  2. Show last used cards;
  3. Make a smart algorithm that defines which cards to show depending on the time of the day, day of the week, stats etc.(if a user goes to the grocery store next to his living on the evening after the office hours and visits the hypermarket on the weekend, we can help by showing proper cards in these time periods);

Within the prototype we decided to stop at the first option in order to test setting change right in the widget itself. You don’t need to make a new screen inside the app for that. But are users that much experienced to find these settings?

Widget user settings

Settings are made with intents (hi to Android devs) — by adding a new widget an intent file is automatically added to the project. Code generator prepares a subclass from INIntent that is a part of SiriKit framework. The intent parameters contain a magic option «Intent is eligible for widgets». There are several types of parameters available; subtypes can also be set up. Because in our case it’s a dynamic list we also enable «Options are provided dynamically».

For different types of widgets we set the maximum elements number in the list: 1 for small and 4 for medium.

This intent type is used as the data source.

Next a set up intent class must be put into a config “IntentConfiguration”.

In case the user preferences are not required (means an alternative of StaticConfiguration class that operates without intent directions).

On the settings screen changeables are title and description.

Widget name must fit into a single line — otherwise it will be cut off.

The allowed maximum length for widget adding screen and widget setup differ from each other.

iPhone 11 Pro Max — 28 (settings) and 21 (from menu)

iPhone 11 Pro — 25 (settings) and 19 (from menu)

iPhone SE — 24 (settings) and 19 (from menu)

The description is multiline. For a very long text in settings the content can be scrolled. While on the “add widgets” screen preview is compressed first and then something weird happens to the layout.

Also background color and WidgetBackground and AccentColor can be changed — by default these can be found in the Assets. They can be renamed if needed in the config of the widget in the Build Settings of an Asset Catalog Compiler group Options via Widget Background Color Name and Global Accent Color Name fields respectively.

Some parameters can be hidden(or shown) depending on the value selected in another parameter via the Relationship setting.

It’s worth noting that the UI for editing a parameter depends on its type. E.g. if we put boolean, then we see UISwitch, if we put Integer, then we get a choice from two options: input via the UITextfield OR step-by-step change via the UIStepper.

Interacting with the main app

The connection is set. Now it only remains where the intent takes the real data from. A file in the common group (App Groups) serves as the bridge to the main app. The main app writes down — a widget reads.

To get a URL to the common group the following method is used:

Save all the candidates as they are going to be used by the user in the settings as a dictionary for selection.

Next the OS must understand that the data was updated. For that we call:

Because calling a method will reload widget content and the whole timeline, use it when the data was really updated in order not to overload the whole system.

Data update

For the sake of tenderest care for user device battery Apple invented a widget data update algorithm with the use of timeline — a snapshot generation. Directly a developer does not control the view, but makes a schedule upon which an OS makes snapshots in the background.

The update goes on the following events:

  1. Upon calling previously used WidgetCenter.shared.reloadAllTimelines();
  2. By adding a widget to the desktop;
  3. By editing settings;

Also a developer has access to three types of policies for timeline updates:

  • atEnd — update after showing the last snapshot;
  • never — only updates upon force call;
  • after(_:) — updates after a defined time period;

In our case it’s enough for the system to make a single snapshot before card data is updated within the main application:

A more flexible option could be used for the automatic card selection depending on the day of the week and time of the day.

It’s worth noting about showing a widget if it is located in a Smart Stack: in this case we can use two options in order to control the priorities: Siri Suggestions or through setting relevance value for TimelineEntry with the TimelineEntryRelevance type. TimelineEntryRelevance contains two parameters:

  • score — priority of a current snapshot upon other snaps;
  • duration — time active when a widget stays up to date and the system shall put it to the top position within a stack;

Both these options and also a widget configuration settings were reviewed in detail on a WWDC session.

We also have to tell you about how to show the actual date and time. Because we can’t constantly update widget content, the Text component was added several styles. When using a style the system automatically updates component content, while the widget is on the screen. Perhaps, in future this approach will be used for other SwiftUI components.

Text supports the following styles:

  • relative — difference between current date and the given date. Note: if a date is given in future, the countdown starts and after that the date shows from reaching zero. Such behavior is applicable for two following styles as well;
  • offset — same as the previous one, but has indication — a prefix with ±;
  • timer — timer view;
  • date — date view;
  • time — time view;

Besides that it’s possible to view the time period between dates just by giving an interval:

Widget preview

At the first view the widget will open in the preview mode: for that we need to return TimeLineEntry for placeholder(in:) method. For us it looks like this:

After that the view is added a modifier redacted(reason:) with a parameter placeholder. By that the elements on a widget are blurred.

We may cancel this effect for some elements by using unredacted() modifier.

Also the manual says that calling a method placeholder(in:) happens synchronously and the result shall return as quickly as possible, unlike getSnapshot(in:completion:) and getTimeline(in:completion:).

Rounding the elements

The guidelines recommend to adjust rounding the elements with the widget rounding. For that the iOS 14 was added a structure ContainerRelativeShape, that allows to apply the container form to the view.

Objective-C support

In order to add an Objective-C code into the widget(we generate barcode images with it), we use a standard function by adding an Objective-C bridging header. The only issue we faced — Xcode couldn’t detect automatically generated intent files after the assembly. So we added them to the bridging header as well:

Application size:

The testing was done on Xcode 12 beta 6.

Without widget: 61.6mb;

With the widget: 62.2mb;

Summary

  • Widgets is a great option to test SwiftUI. Feel free to add them to the project even if the minimum supported app version is below iOS 14;
  • WidgetBundle is used for increasing the number of available widgets. Here is the great example of how many different widgets contains the ApolloReddit app;
  • IntentConfiguration or StaticConfiguration helps to add user settings into the widget directly if user settings are not necessary;
  • Common app folder in the file system for App Groups helps to sync the data with the main app;
  • A developer is given several timeline update policies (atEnd, never, after(_:));

Here we may finalize our thorny path of building widgets on Xcode betas. The only step remaining is to have your app update pass the review by App Store.

--

--