iOS 14 Widget Extension: Box Office Collection

Pallav Trivedi
We Are BookMyShow
Published in
4 min readSep 7, 2020

--

Enhancing the range of extensions to an iOS app, WWDC 20 introduced Widget Extension. These home screen widgets are visually similar to Today Extension (deprecated), except the fact that they can be dragged across the screens, are available in different sizes and must be written in SwiftUI (of course a good move to push SwiftUI into the market).

Let’s dive deeper into these Home Screen widgets by creating one. The use case is ‘Box Office Collection’. Assume an application (say ‘Moviepedia’) which tells you everything about movies (release date, cast, synopsis, IMDb ratings and alot more). Extending these functionalities, how about creating a widget which stays on home screen, fetches the information from server and periodically updates it.

Talk is cheap, show me the code.

Open the Xcode (minimum version 12.0) and create a project for iOS app. We won’t be doing anything in the app. Add a new target viz. ‘Widget Extension’.

Now that the extension has been added, and we got the boilerplate code ready, we can proceed with our implementation.

First things first. We’ll be fetching the movie information from server, so let’s create an API for that. I’m using mocky.io for this (you can use anything, all we need is a JSON response).

I’ll be using this JSON, while you are free to add any number of keys in your response object (go crazy with your imagination).

We have our JSON ready, and it’s time to create a parser. Create a swift file (say MovieInfoLoader), and parse the JSON in it.

We got the API, we got the parser, let’s move ahead towards consuming this response in our widget. Delete the boilerplate code, we’ll write the things from scratch (to understand each chunk in detail).

Because we want our widget to refresh periodically, we need to provide a timeline. Create a struct (say MovieInfoEntry), and conform to protocol TimelineEntry (import WidgetKit for this). Though timeline only requires a date (telling when to update next), we can always pass some extra info (our MovieInfo object in this case).

This timeline will be used by our TimelineProvider. As per the Apple’s documentation, a TimelineProvider creates one or more timeline entries with dates that tell WidgetKit when to display a widget. To render a widget, WidgetKit executes the `content` block of the widget’s configuration, passing the corresponding timeline entry.

Create a struct (say Provider) and confirm to TimelineProvider protocol. To do so, we’ll need to override three methods viz. placeholder, getSnapshot, and getTimeline.

placeholder: A placeholder view is a generic visual representation with no specific content. It’s similar to a placeholder asset for a complication in watchOS, except it’s provided as a SwiftUI view.

getSnapshot: WidgetKit calls this method when the widget appears in transient situations. This method has a parameter, ‘context’. If its isPreview property is true, the widget appears in the widget gallery. Hence, we would supply the sample data in this method, and will avoid the fetching from server.

getTimeline: This method provides an array of timeline entries for the current time and optionally, any future times to update a widget. This is done using a special type viz. TimelineReloadPolicy. TimelineReloadPolicy is nothing but a type that indicates the earliest date when WidgetKit requests a new timeline from the widget’s provider.

As per the Apple doc, WidgetKit may not update the widget’s view exactly at a timeline entry’s date. The update may occur at a later date.

To provide the entry, we will fetch the data from the server and will supply a random object from that array. Also, we want our widget to refresh frequently, we will use after as TimelineReloadPolicy (other two policies are atEnd and never).

Fetching of data and preparing of data source is done. The only thing left is rendering the data into the view. It’s all about making a view using SwiftUI. Let’s quickly do that.

Create a struct (say BoxOfficeWidgetEntryView) and confirm to View protocol. Create a variable entry, for holding the entry provided by provider.

var entry: Provider.Entry

Because we want our widget to render in different sizes, create a variable family.

Small Widget (systemSmall)
@Environment(\.widgetFamily) var family

Widget family provides three sizes for widgets thru WidgetFamily enum i.e. systemSmall, systemMedium, systemLarge. Annotate the body with ‘@ViewBuilder’ (we will using conditional construct in the body) and start building the view.

I’ve used SDWebImageSwiftUI for image loading (you may use anything else, or write your own implementation for that).

We are all set to see our widget in action. Create a struct (say BoxOfficeWidget), confirm to Widget protocol. This will be the entry point and all the components developed till now, will be used here.

Kind is used as an identifier for your widget (your app may have multiple widgets). Scenarios where we want to refresh the widget forcefully, we can use this identifier (kind) in this way

WidgetCenter.shared.reloadTimelines(ofKind: "BoxOfficeWidget")

This can only be done from the main app.

Source code can be found at this Git Repo.

Happy Coding!!!

--

--

Engineering Manager @BookMyShow ; Previously @ Hungama Digital Media, Built.io, Reliance Jio