Build a simple widget for your iOS home screen

Roger Tan
Kin + Carta Created
8 min readAug 11, 2020
Example of widget from Meet WidgetKit at WWDC 20

Introduction

The widget has existing for a while in the desktop world. It was popularised by Apple since the first launch of Mac OS X Tiger in 2005. Widgets are mostly used to inform the user of the latest information at a glance. The first format of the widget to iOS has been Today Widget. You probably see this when you swipe to the left of your home screen or your Notification Center. But widgets were not available on the home screen until it was announced at the WWDC 2020 for iOS 14 whereas Android has had Widget on the home screen since the launch of Android in 2007.

For this post, we will build a simple riddle widget from an existing project.

Prerequisite

- iOS 14
- Xcode 12

How does the widget work?

Widget works as an extension of your app. It cannot work as a standalone app itself. Widgets are available in three sizes (Small, Medium, Large) and could be static or configurable. A widget is limited in terms of interaction. It cannot be scrollable, only tappable. A small widget can only have one type of interaction area whereas a medium and large widgets can have multiple tappable areas of interaction. A widget can only be written with SwiftUI.

Architecture

Let’s see the architecture of an app with WidgetKit. The app is basically your UIKit or SwiftUI project. The widget works like an extension outside of your app. It can be displayed on your iOS/iPadOS home screen or Notification Center in macOS.
The shared logic could be some files shared between two target memberships or your shared framework.
I recommend using a shared framework that contains some reusable code. It also helps your codebase to have a responsibility concern.

Before building the first widget, we will use an existing app that doesn’t have WidgetKit at the origin. Please clone the project from this git repository. Then open RiddleProject.xcworkspace.

The project should contain everything that we need for later.

Create a new Widget Target

To create a new target, press the + button under the target membership.

Then select Widget Extension then press Next.

Enter a Product Name, here we will use RiddleWidget. Then uncheck Include Configuration Intent as we don’t want to make a configurable widget and press Finish.

On the next screen, you will have a dialogue to Activate “RiddleWidgetExtension” scheme? Please confirm by pressing Activate so we can use to debug our widget.

Understanding the Template file of the widget

Open RiddleWidget.swift

TimelineProvider

TimelineProvider is an important piece of the widget. It provides to iOS all the information of a future update for a widget.

public func placeholder(in context: Self.Context) -> Self.Entry

This placeholder method is called when the widget is displayed for the first time that could be using the modifier redacted. I would recommend for this method to provide some dummy data so it can be previewed quickly.

public func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ())

This snapshot method is mostly called when the widget is in a transient state like when a user is adding a widget. If the widget is on preview mode, I would recommend to use some dummy data so the user can preview how it will looks like if they add to their home screen.

WidgetKit provides to the parameter a context, so you can know if the widget is previewed from the gallery, the widget family size, the real size of the widget.

The completion tells the system that you finished your asynchronous task.

public func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ())

This method is called most of the time when the widget is displayed on the home screen, it needs to return a timeline of entries. It’s this method that you can tell iOS when it’s appropriate to update a widget. For example, a weather app, you might update once every hour, but for a Stock, or Mass Metropolitan Status, you might want to update more frequently.

The Timeline object that you need to return on the completion takes two parameters. The timeline entries and the policy. The policy on how the update of the widget can be never, after the last date of the entry of timeline or after a specific hour.

SimpleEntry

SimpleEntry is just a simple object that conforms to the protocol from TimelineEntry. The date is important as it will tell to iOS when it needs to update the widget. You probably will add as an extra property your model for the view so you can bind easily the data to your widget.

PlaceholderView (Xcode 12 Beta)

Just ignore this object. Placeholder parameter in StaticConfiguration or IntentConfiguration is deprecated since Xcode 12 Beta 3. So it likely it won’t be used by WidgetKit.

RiddleWidgetEntryView

EntryView is just your SwitUI view of your widget displayed in your home screen with the current timeline entry use as a parameter.

RiddleWidget

RiddleWidget conforms to a protocol Widget. It required to have a body of some type of WidgetConfiguration.

There are two types of WidgetConfiguration that Apple SDK provide. Here we will use a StaticConfiguration as we don’t want to have a configurable widget.

If you want to have a configurable widget by the user. You will need to create an IntentConfiguration but also a Siri Intent. We won’t cover here as we want to build a simple widget.

Add the shared library to the widget

The first part is to add a shared library to our widget extension so we can reuse our model and colours.

If you want to learn more about how to build a Swift Package Manager Module. I can only recommend reading this article from my colleague Kane.

Firstly, make sure to choose the right target by selecting RiddleWidgetExtension, then Press the + Button under Framework and Libraries.

Then select CommonLibrary and press Add.

Now, you should be ready to use the same model and colours palettes from the common library between the main app and widget extension.

Update the UI
Let’s create this widget. Before to update our widget, we need to import our shared library on top of struct Provider: TimelineProvider { } by writing import CommonLibrary.

Then now, the first part will be to update the body property of RiddleWidgetEntryView. We won’t use the model at the moment. We will use a static text and you should be able to preview. We will also add modifier .unredacted() so won’t transform as placeholder view during a loading state for example.

Update the RiddleProjectPreview so we can preview the widget in Placeholder mode and normal mode.

So you should be able to see the widget in two states. That’s fantastic right?

Update the model SimpleEntry

In order to retrieve the riddle, we need to update the struct object and add an extra stored property for the riddle. Xcode must complain that a missing argument parameter in snapshot, timeline methods of provider and preview methods.

Bind UI with entry
Let’s make sure that the RiddleWidgetEntryView can bind the value of the entry object passed it.

Before building and run. Let’s make sure that the widget is only available for the widget of medium size by adding an extra modifier to our StaticConfiguration by using .supportedFamilies([.systemMedium]). And at the same time, we can update the Display Name of the widget and his description.

Now, build and run the widget. You should see a nice widget on your home screen.

But If I tap to the widget at the moment, it just opens the app but it doesn’t show the riddle displayed from the widget. But can we display the relevant content?
That’s what we will learn next with Universal Links.

Handle a universal link from a widget

There are two ways to open the app from a widget. If your widget is medium or large-sized, you can use Link in a portion of your view. It works similarly to Button where the action is specialised to open a web link. In our case, we will make simple and use the modifier widgetURL(url: URL) that use all the portion of the widget to open the app.

Let’s add the widgetURL modifier inRiddleWidgetEntryView so the main app will know if it needs to handle the universal link.

To handle the universal link, your view needs to register a handler to invoke when the view will receive an URL. We will use the method .onOpenURL(perform: action: @escaping (URL) -> ())

Let’s register our handler like the following code so we can extract the id from the URL, find the relevant riddle matching the id and update the content view.

Now let’s build and run, tap to your widget and you should be able to see the relevant riddle in your main app.

Conclusion

Learning and building a widget is easy to do. WidgetKit is one of the hot topics of this WWDC 20. By making more visible on the home screen, Widget will be more useful than before of the old Today Widget. When you will create a widget, don’t bring too much information and interaction. Keep it simple for your user.

--

--