WidgetKit is a framework which is introduced during WWDC 20, the first online-only developer conference. Using WidgetKit, developers are able to create beautiful widgets which can be displayed between the apps on the homescreen.

Widgets should be written in SwiftUI, which is fine because widgets are only available for iOS 14 and above. By writing your widgets in SwiftUI, you’re also able to use Xcode Previews to preview every size of the your widget.

New to SwiftUI? Read my blog post with an introduction to SwiftUI.

Widgets support dark mode by default, so it’s important to test your widget in both styles to be sure your widget is presented right.

About WidgetKit

Using WidgetKit, you’re able to create a widget based on two different types of configuration. The StaticConfiguration needs to be used when no configuration is needed from the user. If a configuration from a user is needed, you need to use a IntentConfiguration. For now, we only focus on the StaticConfiguration.

There are three different size styles for widgets, which are small, medium and large. By default all size styles are enabled, but you’re free to disable size styles.

New data for widgets is queried, which means you’re not full in control when you want to display new content inside your widget. The more your widget is shown, the more it will be reloaded. Reloads can be triggered using a background notification, timeline setup or an app-based action.

Scrolling in widgets is not supported, just as showing videos or animated images. The whole widget is a tap target, which can trigger a deeplink if needed.

Getting started…

A widget is an extension for an already existing project. If you don’t have an existing project yet, create a clean iOS project in Xcode.

Next, we can add the Widget Extension from the menubar ‘File’, ‘New’ and select ‘Target’. Search for ‘Widget Extension’ and press ‘Next’. Now you can give your widget a name and the setup is ready!

Run the widget target on a simulator and you’ll see that a small empty widget which is visible on the homescreen!

Understanding the WidgetBundle

A widget is recognized by the system when it’s part of a WidgetBundle. This bundle includes the widgets that should be displayed on the homescreen of your users.

Let’s take a look to the code which is needed to create a WidgetBundle.

import WidgetKit
import SwiftUI

@main
struct CuracaoAirportWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        UpcomingArrivalsWidget()
    }
}

First we import the WidgetKit and SwiftUI libraries. After that, we’re able to define our widget class, which extends from the WidgetBundle parent class. The body of the WidgetBundle contains the widget we would like to be displayed. You’re able to return multiple widgets in the body, so the system can select different widgets for the user based on AI.

Defining a widget

Before we’re able to return a widget in the WidgetBundle, we need to create one. In this blogpost, we will create a StaticConfiguration widget, which means we don’t expect input from the user before we’re able to display the widget to the user.

struct UpcomingArrivalsWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "UpcomingArrivals", provider: Provider(), placeholder: UpcomingArrivalsWidgetPlaceholderView()) { entry in
            UpcomingArrivalsView()
        }
        .configurationDisplayName("Upcoming arrivals")
        .description("Displays the upcoming arrivals on Curacao!")
    }
}

In the code above, a new widget named UpcomingArrivalsWidget is created. The body contains the initialisation of the widget. A StaticConfiguration widget needs the following input:

  • Kind -> A unique identifier for the widget. You can fill in any identifier you want. You need this identifier if you want to reload your widget from code.
  • Provider -> The provider is needed to determine the timeline for refreshing your widget. This will be explained later in this blogpost.
  • Placeholder -> This is a view which is displayed to the user before your is loaded with content
  • Content closure -> The content closure contains the widget view that needs to be displayed to the user (for all sizes).

So in the content closure we’re able to build our widget view. A basic implementation will look like the code below and will show a text in the center of the widget frame, with a green background.

struct UpcomingArrivalsView: View {
    var body: some View {
        Text("Zonneveld.dev")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green)
    }
}

Visual example of the view inside a Widget:

Widgetkit iOS widget

You are able to recognise the size of the widget by defining an Environment for widgetFamily. After that, you are able to check the widget size style in the body. The size can be systemSmall, systemMedium or systemLarge.

Example:

struct UpcomingArrivalsView: View {
    @Environment(\.widgetFamily) private var widgetFamily
    
    var body: some View {
        ZStack {
            if widgetFamily == .systemSmall {
                Text("Show small arrivals here!")
            } else {
                Text("Show arrivals here!")
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.green)
    }
}

The provider contains the refresh logic

Now the widget is created, we need to define the provider to define the refresh moment. The full code example is below which can be copied in your widget extension.

Using the function timeline the refresh entry is created. In the example, the refresh moment is requested for each hour. Again, the refresh moment is decided by the system, so it’s not guaranteed that your widget will refresh on the moment you prefer.

Code example:

extension UpcomingArrivalsWidget {
    struct Provider: TimelineProvider {
        func snapshot(with context: Context, completion: @escaping (Entry) -> Void) {
            let entry = Entry(date: Date())
            completion(entry)
        }
        
        func timeline(with context: Context, completion: @escaping (Timeline<UpcomingArrivalsWidget.Entry>) -> Void) {
            var entries: [Entry] = []
            
            let currentDate = Date()
            let entryDate = Calendar.current.date(byAdding: .hour, value: 0, to: currentDate)!
            let entry = Entry(date: entryDate)
            entries.append(entry)
            
            let timeline = Timeline(entries: entries, policy: .atEnd)
            completion(timeline)
        }
    }
}

Xcode Previews

Using Xcode Previews, you’re able to view previews of your widget inside Xcode. It helps you a lot if you define previews for all sizes for light dark mode and dark mode (so 6 previews!).

A example of a preview is show below:

struct UpcomingArrivalsWidgetSmall_Previews: PreviewProvider {
    static var previews: some View {
        UpcomingArrivalsView()
            .previewContext(WidgetPreviewContext(family: .systemSmall))
            .previewDisplayName("Small widget")
            .environment(\.colorScheme, .dark)
    }
}

In this example, I created a preview with the WidgetPreviewContext set to systemSmall, which means the preview will display a small widget. This can also be se to systemMedium or systemLarge for other widget sizes.

Also the colorScheme is set to dark to get a dark mode preview, but you’re also able to set this to light (which is the default preview style).

Example of widget previews:

Xcode Previews widget widgetkit

Conclusion

When creating a widget, you are full in control what to display inside the widget and how the widget should be styled. Widgets have a prominent location on the screen, which means it offers a large entrance to your app.

When creating widgets, Xcode Previews are very powerful to view all the possible sizes and styles, so you can quickly check how all sizes will look in both light and dark mode.

There is a lot more to learn about WidgetKit, like advanced refresh situations, setup an IntentConfiguration and more. I will write soon more about WidgetKit!

🚀 Like this article? Follow me on Twitter for more SwiftUI related blogs!

Categories:iOS