build your first widget in iOS 14 with WidgetKit

in the first ever fully-online worldwide developer conference wwdc20, apple announced the introduction of widgets in home screen, with ios 14. this, along with moving apps to app library was an unexpected move since the home screen has not changed a bit since the first version of ios was introduced.

build your first widget in iOS 14 with WidgetKit 1

in the meet WidgetKit WWDC20 session, we saw Nahir and Neil from apple walk us through the newly announced widgets in home screen (which are fully built with SwiftUI and interoperable between iOS 14, iPadOS and MacOS Big Sur, btw). the summarised text version of the key points in the keynote is available in my another post over at better programming on medium, a 4 minute read. do give it a read up because this post is going to be purely technical, building up over the concepts i covered in the article linked above.

prerequisites

ensure that you have
1. a mac device running macos catalina 10.15.5 or later
2. xcode 12.0 beta 1 or above installed (though for some reason i was unable to preview widgets in swiftui preview pane in xcode 12.0 beta 1, which was solved once i moved to beta 2)
3. basic knowledge of swiftui. you might find this free course at raywenderlich helpful

today is a great day to build widgets with widgetkit!

open the xcode project you want to add widgets to. if you don’t have one already, would be completely fine to create a new blank one.

the app can be made in UIKit or SwiftUI, since widgets are SwiftUI only, I am proceeding with SwiftUI interface.

create a blank xcode project

once done, add a new Widget Extension by going to File > New > Target > select Widget Extension

adding widget extension to app

you have a checkbox to include Configuration Intent. in this tutorial we are going to cover Static Configuration, since we don’t want to give an option for users to edit something in the widget. uncheck that checkbox and select Finish.

adding static widget

also select Activate on the following screen

build your first widget in iOS 14 with WidgetKit 2

navigate to the swift file under the newly created extension, to see the preview of the widget in the preview pane.

build your first widget in iOS 14 with WidgetKit 3

you can also preview how the widget looks in your phone by pressing on the play-like icon above the widget in the preview canvas.

understanding the boilerplate

looking at the skeleton of the widget class, you would see a struct Provider of type TimelineEntry having two functions, snapshot and timeline. We will come back to these at a later point of time.

then, there is a struct SimpleEntry of type TimelineEntry with a date property. this is usually the structure for data to be shown in the widget.

then there are two views, PlaceholderView and Static_WidgetEntryView. these are the views which are ultimately displayed on the home screen inside the widgets you are going to create. PlaceholderView is shown when there is no data to display, for eg. when a user has just installed and not opened your app, yet places a widget on the screen. 

build your first widget in iOS 14 with WidgetKit 4

the other Static_WidgetEntryView is shown under normal conditions, which is a view designed to look good on the home screen, with data filled in. we will be fully designing the view to be shown in the widget, in the next few minutes.

then there is this important struct of type Widget

here, the custom widget view is returned at line 7, which needs to be modified based on the view you want to show in the widget’s real estate. the configurationDisplayName and description is shown to the user while he adds widget to his home screen, so you might want to modify that as well.

finally, the PreviewProvider struct

is responsible for showing your widget in the preview canvas.

let's play!

timeline provider:

Timeline Provider is the engine of the widget — the provider class is mainly responsible for providing a bunch of views in a timeline, along with an option to give a snapshot of the widget. for eg. when user wants to place the widget, a preview of the widget is shown to the user which is obtained from snapshot, whereas after being placed on the screen, timeline will return the views to be displayed on the home screen.

since widgets load in the home screen, Apple doesn’t want users to look at a bunch of loading widgets. hence, widgets in iOS 14 are just a bunch of views bundled in a timeline.

when the app is opened, it gives the system a bunch of views along with a time label. for example, if your app wants to show countdown to an event in a widget, your app needs to make a bunch of views from that point of time till the event date, and tell the system which view to display at what time. for example, if the event is 4 days away, the app could send in 5 views:
view 1 -> to be shown today > has contents “Event is 4 days away”
view 2-> to be shown tomorrow > has contents “Event is 3 days away”
view 3-> to be shown the day after tomorrow > has contents “Event is 2 days away”
view 4-> to be shown a day before the event > has contents “Event is 1 day away”
view 5-> to be shown on the day of event > has contents “Event has started”

iOS displays the appropriate view based on system time, and hence the widget ends up looking right at any point of time.

also, creating such bunch of views is cheap task, and doesn’t require constant compute timehelping iOS preserve battery as well, adding to the smooth performance.

looking at the code,

on line 14, 5 entries are created, one for each hour. For each entry, a view is created with Static_WidgetEntryView

these views are then provided to the iOS System, so that appropriate views can be shown basis time.

you can try testing this part out by replacing byAdding: .hour with byAdding: .minute and test in simulator. After the widget is shown, for the first five minutes, widget view will update once every minute, after which the widget wont change and will keep showing the last view the widget extension sent to iOS.

designing the widget view:

now, let’s make a custom view whose contents will be rendered into the widget.

at this point, we are going to segregate the widget view and provider, since everything is in same swift file. we know that the TimelineProvider is responsible for building views and showing it onto a timeline (a timeline is nothing but a time labelled series of views, which get shown on the home screen based on current time. 

more theory in my previous article, linked at the top of this page) and the main struct Static_Widget is responsible for defining the widget views in the application, we will be moving the widget view to a new file.

begin by creating a WidgetView swift file by adding a new file > SwiftUI View > Next, name it WidgetView. Ensure it has the widget extension checked under target extension, and click Create

adding swiftui view to widget

the swift file created would have template code for a SwiftUI View, however we want to design a widget here. In the newly created file, import WidgetKit as well.

now, let’s look at the widget to be created

widget to be created

a funky static widget showing a static weight, and a relative time which needs to be kept up-to date.

since the widget needs only two variables, we will create a struct to store them. also create an extension of the struct to have a preview data to easily be able to fill in WidgetData, just for demonstration purposes.

now that the data structure is added, let’s prepare the view and preview for the widget. create a WidgetView and a provider for preview as follows

build your first widget in iOS 14 with WidgetKit 5

here you will notice that the preview has WidgetView previewed with .previewContext(WidgetPreviewContext(family: .systemSmall)) which renders out the view like a widget. Here the family can be changed to systemMedium and systemLarge to preview contents in medium and large formats as well. Adding in previews for other widget families too, the code for WidgetView gets updated to

begin with adding views to the body of the widget on line 17. looking at the design, we need two labels, one for static text “Weight” and another for weight from data: WidgetData. so after adding two Text(“”) views in a VStack with a Spacer() in between, lets also add font and foreground colours to make them look good.

build your first widget in iOS 14 with WidgetKit 6

to make the contents of widget left aligned, set the alignment of the VStack to leading

modifying the weight view

now, we need this VStack to be placed inside another view with a background color of cyan, so let’s put the VStack in a HStack and add the background color as well. also, a little bit of padding might look nice.

adding background to weight view

here, we have used ContainerRelativeShape().fill() for the radius of the background. its important to note that acc. to widget design guidelines, its not recommended to use fixed rounded corners, instead use CornerRelativeShape which draws its radius concentric to its superview.

to make the HStack fully occupy the view, let’s add a Spacer(minLength: 0) along with the VStack in the HStack.

build your first widget in iOS 14 with WidgetKit 7

now, we have almost achieved half of what needs to be built

widget to be created

to build the last checked view, adding a new VStack to add the two Text views

added info view

here we have used the new Swift syntax for Text to display date relative to current time. for example, Text(“data.date, style: .relative”). Using this with a widget ensures that time shown in the widget is relative to current system time and is always updated every second. widget views aren’t meant to update every second, so iOS gives an option to place a dynamic time text, the per second updates are handled efficiently.

now finally placing them in a ZStack to get background color of yellow and with some padding for VStack inside it, let’s extract the two subviews to WeightView and LastUpdatedView using Xcode

build your first widget in iOS 14 with WidgetKit 8

once that’s done, the final code for WidgetView would be

and the created widgets would look like

build your first widget in iOS 14 with WidgetKit 9

by now you might have noticed that we have designed the view for .systemSmall family and widget isn’t looking good for other sizes. To make the .systemMedium widget look more appealing, lets add in a relevant icon which is shown only for .systemMedium widget (Read how to render part of view for widget based on family type)

first, create an environment variable with

@Environment(\.widgetFamily) var widgetFamily

the value of this variable is set as .systemMedium if the user has placed medium sized widget on his home screen. In that case, you can use

if widgetFamily == .systemMedium {
//Render view here
}

you can add in the image to either side of the current content.

build your first widget in iOS 14 with WidgetKit 10

exercise: Design the widget to look good for .systemLarge format too, currently it looks like

time to make systemLarge widget prettier

for now, we won’t be supporting large size for widget. to support only desired widget size families, head to the main function and add .supportedFamilies([.systemSmall, .systemMedium]) to ensure users can’t place a .systemLarge widget on their home screen.

putting it all together

now that we have designed the widget view, all that is left is to add this widget view to app’s widget extension. remember the @main function we talked about earlier? (Scroll to “Understanding the boilerplate” section above). to get the WidgetView as a widget in your app,

add widgetview to list of widgets

add WidgetView(data: .previewData) as shown replacing the default widget view, and run this on the simulator

widgetkit on home screen

and voila! we have your first widget added to your iOS application!

you can download the completed project here on Github — https://github.com/iAkashlal/SwiftUI-Widgets

where to go from here?

congratulations on adding your first widget to your iOS application. what we have discussed till now is just the bare minimum of what you can do with widgets, so thought I’d add a few more things that can be accomplished with WidgetKit.

Timeline Reload Policy

TimelineProvider provides views of the widget for a series of time. however after the last entry has been provided and shown, TimelineReloadPolicy can be used to assist WidgetKit with scheduling updates so that widget can be refreshed. TimelineReloadPolicy is a struct with

struct TimelineReloadPolicy{
atEnd
after(date)
never
}

atEnd — after last entry is displayed, an update is scheduled. When that update happens, iOS can request for subsequent entries from timeline provider, which can happen in a cycle.

after(date) — an update is scheduled at the date provided — irrespective of currently existing entries

never — system will not independently update a widget. we can set when the widget reloads via the WidgetCenter API.

apart from this, the system intelligently schedules updates — with onboard intelligence and based on user behavior, ensuring widgets are always up-to date. You can learn more about this in Widgets — Code Along (Part 2)

relevance

relevance is an optional property on timeline entry, which assists WidgetKit in helping decide which widget to show at the top when multiple widgets are placed in a stack. while calculating entries in a timeline, you can add a float value for relevance for each entry, assisting WidgetKit with the most relevant of all submitted entries for your widget. this is illustrated in part 2 of Widgets — Code Along WWDC20 session.

configuration

you would want configuration in your widgets if you want to give the user an option to edit widget and select from a few available options that affect how the widget is displayed. WidgetKit configuration is driven by SiriKit, with the core technology for configuration being INIntents. more info available in WWDC20 Add Configuration and Intelligence to your Widgets session.

deep linking

.systemSmall widget’s whole real estate can deeplink into one section of your app using .widgetURL modifier on the view, while .systemMedium and .systemLarge widgets can either use .widgetURL modifier, or SwiftUI Link API to have tappable zones to link to different pages. This is shown in WWDC20 Widgets — Code Along session (Part 3)

multiple widgets (widget bundles)

if your app wants to have multiple widgets, you can’t keep adding multiple widget extensions. and since one widget extension can have only one main method, you cant use the way we used to add more widget views.
for this purpose, you can use WidgetBundle and move the main tag there, for example

@main
struct MultipleWidgets: WidgetBundle{
@WidgetBundleBuilder
var body: some Widget{
WidgetView1()
WidgetView2()
}
}

with WidgetView1() and WidgetView2() being valid SwiftUI Views.

resources

this piece of content would be impossible without:

understanding WidgetKit in iOS 14

build SwiftUI Views for widgets

widgets — code along (Parts 1, 2 and 3)

more on WidgetKit

4 thoughts on “build your first widget in iOS 14 with WidgetKit”

  1. Hi Akashlal,

    Thanks for sharing this great knowledge about WidgetKit. Do you know if we can add user analytics tool to track events? Like widget is displayed; User click one tappable target…

    1. hey Jerry,
      i have never tried adding user analytics. Since WidgetKit is an extension and not a target, i am guessing the only way left is to add some parameter to deeplink url and get the main app to track that tap and add it to say firebase analytics

Leave a Comment

Your email address will not be published. Required fields are marked *