Say Hello to Widgets With iOS14

Ritu Bala
The Startup
Published in
8 min readAug 18, 2020
source: MacRumors

At Worldwide Developer Conference Apple announced much awaited feature for iOS devices, Widgets!! Yeah finally!! A Widget displays important and relevant content from your App that is quickly available right from the Home screen(iOS Devices) or Today views(iPadOS, MacOS).

Widgets are powerful and very easy to incorporate in your application with WidgetKit framework.

Pre-requisite:

You will need Xcode version 12.0 onwards to create widgets for your application. As of now, I am creating this widget on Xcode 12.0 beta version. You will need iOS version 14.0 onwards to test on real device. Simulator is also just fine. Also, widgets are SwiftUI views, so you need to have a basic understanding of SwiftUI. Your entire codebase need not to be in SwiftUI, it works perfectly with projects written in Swift and Objective-C.

Let’s Get Started:

Alright then, lets get started:

  1. Open your app project in Xcode and choose File > New > Target.
  2. From the Application Extension group, select Widget Extension, and then click Next.

3. Provide name for your widget SimpleWidget. There are two configuration options available for widgets: Static Configuration and Intent Configuration. For the scope of this article we will be focussing on Static Configuration. So, Uncheck ‘Include Configuration Intent’ option which defaults to Static Configuration.

5. You will find a folder with the name SimpleWidget in your Project Navigator.

6. Now, run your project on simulator once. Press Home, long press on your app icon.

7. Select Edit Home Screen.

8. Press + icon. It will open Widget Gallery for you.

9. Here you will find your application. Select your application, and tada!! here is your very very simple widgets already created for your application, without writing a single line of code. Pretty amazing isn’t it. Go ahead and add them on your home screen.

10. So far so good, right! Let’s get dirty with the code now and understand what’s going on under the hood by creating our own widget. Let’s create one Advanced Widget from scratch. Choose File > New > File > Swift File. Select Next, provide name AdvancedWidget, select SimpleWidget group, make sure you select SimpleWidgetExtension as Targets for your swift file. Choose create.

Now we are going to create an Advanced Widget, brace yourself!

  1. We are going to use WidgetKit and SwiftUI. Go ahead and import these frameworks.
  2. Create with a struct AdvancedWidget that conforms to Widget protocol.
  3. Now your protocol is screaming at you to provide configuration for your widget. Don’t worry we will handle it slowly and steadily, stay with me. Alright, provide the body property to calm the compiler down a bit.
  4. Compiler still doesn’t seems to be happy, because body property needs to return WidgetConfiguration. To initialise Static Configuration, we need to provide following information:

a. Kind: A descriptive unique string that identifies your widget.

b. Provider: It is an object that conforms to TimelineProvider protocol that defines timeline telling WidgetKit when to render the widget.

c. Content Closure: A closure containing SwiftUI view that your widget is going to display. Provider will pass a TimelineEntry parameter to this closure that can be passed to SwiftUI view.

Too much to process, ehh?? It’s okay, will implement these step by step:

First, declare a constant String property named kind in your AdvancedWidget struct and pass it to StaticConfiguration first parameter.

Second, create a struct AdvancedProvider conforming to TimelineProvider protocol. Pass an instance of AdvancedProvider to the StaticConfiguration second parameter.

Before moving on, let’s complete our AdvancedProvider struct implementation.

A timeline contains a custom TimelineEntry type. Let’s create our timeline entry. Create a struct AdvancedEntry conforming to TimelineEntry protocol. Compiler screams yet again, arghh!! Luckily enough this time, the fix is simple, just click on what compiler suggests using Xcode FixIt. It will add the required date property and you are good to go, umm just here! :D Go ahead and add one more String property named advancedString. You can add as many properties you want according to what your widget wants to display as this entry instance will be passed to your SwiftUI view. Your AdvancedEntry struct should look like this:

And we are done with the our timeline entry. Let’s get back to our AdvancedProvider.

Define typealias Entry as AdvancedEntry you just created. Widget displays preview snapshot in the widget gallery. To provide preview for your widget you need to implement following method:

func snapshot(with context: Self.Context, completion: @escaping (Self.Entry) -> ())

Create an AdvancedEntry instance and pass it to the completion block.

Lastly, you need to provide timelines for your widget, implement the following method:

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

This method decides when your widget will update it’s content. You need to pass a Timeline instance to the completion block. Timeline instance expects an array of Timeline entries and a reload policy.

Let’s get introduced to reload policies:

.atEnd: Widget requests a new timeline after the last date specified by the entries in a timeline.

.after(date:): If there is a different date when WidgetKit should request a new timeline. Say if you want to request a new timeline after certain Date.

.never: Widget will never ask for the new timelines.

Go ahead create an instance of AdvancedEntry and pass it to Timeline instance. This instance will be passed to widget’s view.

When WidgetKit displays your widget for the first time, it renders widget’s view as a placeholder. For this you need to implement following method:

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

This method expects to return an instance of AdvancedEntry. Go ahead, create one and return. And, we are done with the our AdvancedProvider. Your AdvancedProvider struct should look like this:

Third, coming back to AdvancedWidget, content closure expects a SwiftUI view, let’s create one! Create a struct AdvancedView conforming to View protocol. Implement the body property, and customise the view according to your needs. For the simplicity of this article(as it focuses on widgets rather than swiftUI), I am going to return a simple text. The text is going to be provided by the AdvancedProvider’s getTimeline: method through the AdvancedEntry. Create a property entry of type AdvancedProvider.Entry. Return text field from body of the AdvancedView. Your advanced view should look like this:

Finally, back to square one, pass this AdvancedView to content closure of the static configuration. Lastly, let’s add some personality to our AdvancedWidget by adding .configurationDisplayName and .description modifiers to our static configuration. You should provide meaningful text here. Your AdvancedWidget struct should look like this:

All good! Compiler also seems to be happy now! :)

Go ahead and run your code on the simulator and checkout your advanced widget. Am waiting!!

Surprised, ehh?? Didn’t see advanced widget in the widget gallery? Hmm.. What a scam. That’s because we didn’t tell widget extension that now AdvancedWidget is the entry point instead of SimpleWidget. To do so, add @main at the top of your AdvancedWidget struct. And remove @main from SimpleWidget struct as there can be single entry point. Now run your app. Waiting again!!

So finally you did see your advanced widget. Congratulations on making the most advanced widget ever!! Pun Intended. Checkout source code here.

Bonus:

  1. By default your widget supports all sizes i.e small, medium, large, but you can restrict according to your requirements like this:

2. You can provide different SwiftUI views to different families by using environment value.

@Environment(\.widgetFamily) var family

You can write a switch case to supply different views for each family as follows:

For the simplicity I have just added another text in a VStack. Feel free to be creative.

3. Wondering about the SimpleWidget you created earlier?? Single app can support multiple widgets using WidgetBundle. If you want your app to support AdvancedWidget as well as SimpleWidget, you can simply do as follows:

Mark WidgetsDemoBundle as entry point instead of AdvancedWidget. Now run it again. You will be able to see all families of SimpleWidget as well as AdvancedWidget. This is super cool, isn’t it?

4. You can manually reload your widget timelines from your app by using:

Tips:

  1. Widgets should not be treated as mini apps. It should display relevant, glanceable content that user can quickly go through without opening the app.
  2. Always support the sizes that fit the requirements. It is not recommended to support large sizes that are just a replica of the smaller widget. Larger widget should display additional information.
  3. You must run your app at least once to see the widget in the widget gallery.
  4. Widgets don’t have scrollable content, animations and custom interactions.
  5. Your widgets should display content as quick as possible in preview as well as on the real widget. Who doesn’t hate loaders?!?

Checkout part-2 of this series for advanced features like Networking, Deeplinking and Intent Configuration here.

PS: This article is written for iOS 14 beta with Xcode 12.0 beta 4, minor syntax changes are expected in future.

I hope you found this article worth your time!!

Thanks,

Ritu

--

--

Ritu Bala
The Startup

Sr. Software Developer — iOS, Cricket and Chess Enthusiast, Occasional Reader.