Creating a Static Widget for iOS 14
Explore the process of adding a widget to an existing code base.
Widgets provide a light-weight user experience for iOS users directly on the home screen. Users do not need to launch the application, so the interactivity is limited. Apple notes that widgets are not lightweight versions of apps but are used to complement the applications’ behaviors. Widgets can be quite simple to create, and they can be configured based on user interactivity with SiriKit.
Note: This article assumes familiarity with SwiftUI, Swift and basic iOS development skills. Additionally, as iOS 14 is currently in beta, this code is subject to change.
This article will show how easy it can be to set up a widget with an existing codebase. The example application provides users with an interface to log water intake to the Health app. First, clone the repository with the starter code on the main branch. To see the final code, switch to the widget-complete branch.
What is a widget anyway?
Per Apple, there are 3 necessary components for a widget: a widget configuration, a timeline provider and a view. The widget configuration is returned in the body of the actual widget. This is where the kind, provider and views are configured. This is also the area where the supported widget sizes are declared.
There are static configurations and intent configurations. Static configurations do not have any user-configurable properties. Intent configurations allow user-configurable properties using SiriKit’s custom intents to define properties. This app will use a static configuration, as custom intents are not needed. An intent configuration would be more appropriate for a weather app where the user can enter their zip code.
The timeline provider determines when the widget will update, and the views consist of the actual UI displayed as the widget.
Widget extension setup
Widgets are created with widget extensions, so select File, New, Target, Widget Extension. The name corresponds to your application, and add “WidgetExtension” (i.e. WaterloggedWidgetExtension).
Make sure to uncheck the 'Include Configuration Intent' box if it is already checked, as this will not be necessary. Finally, ensure that WaterloggedWidgetExtension is selected for running otherwise the widget will not be launched on the simulator.
Rendering a widget: TimelineEntry
First, create a WaterloggedEntry struct that conforms to the TimelineEntry protocol. This struct holds on to a date for the TimelineProvider to provide to WidgetKit to update the widget. It also includes the daily water total as a Double.
Note: Because the app requests access to HealthKit, the Widget extension will not have to do so. This also means that the app must be launched at least once before the widget populates with water consumption data.
Rendering a widget: TimelineProvider
Next, create a Provider struct that conforms to TimelineProvider. Timeline provider requires two methods for protocol conformance: snapshot(with context:) and timeline(with context:). Additionally, the loadWater function is used inside timeline, and the healthStore supports that functionality.
WidgetKit calls snapshot when the widget appears in transient situations (per the documentation). This may happen when it’s displayed in the widget gallery. Apple recommends showing sample data as a placeholder if data needs to be fetched from a server.
The timeline method provides WidgetKit with one or more timeline entries and a reload policy, which tells WidgetKit when to refresh the timeline. This is where the entry is actually created, and the data is loaded from HealthKit.
The loadWater method reads the daily logged water data from HealthKit.
Rendering a widget: Entry view
The next step is to set up the view to be rendered when the widget is launched. Create a WidgetEntryView that conforms to the View protocol. Using the new @AppStorage property wrapper to easily store and retrieve values from UserDefaults, the dailyTarget information is easily accessible. Note that an App Group is needed to share data from UserDefaults between the app and widget extension. This must be configured per each target in the Signing & Capabilities section. Provide a name for the app group, and make sure it matches the app and extension.
The struct needs an @Environment variable to allow for configuring the view based on the size of the widget requested. It also requires a variable for the Provider.Entry, which will be the WaterloggedEntry type. The entry gives us access to the dailyTotal. The ZStack is used to add a background gradient, and the view is configured according to the widget size requested. This is possible due to function builders now supporting switch statements in SwiftUI.
Rendering a widget: The widget itself
Finally, it is time to create a WaterloggedWidget struct that conforms to the Widget protocol. Next, initialize a kind variable that matches the name of the file, or it may crash on launch.
The widget protocol requires a body computed var that returns some WidgetConfiguration. Because this widget does not require user-configurable properties, use the StaticConfiguration type. The StaticConfiguration takes a kind, which is defined in the struct. It also takes a provider, which was previously defined as the Provider struct. Finally, it takes a closure with a TimelineEntry, which is where the WidgetEntryView should be created with the entry passed in. The supportedFamilies can be set as a modifier on the configuration. It takes an array of WidgetFamily types.
The @main attribute denotes this struct as the entry point to the Widget Extension. When it is launched, this struct configures the widget and provides it to the system.
Previewing a widget
Previews for widgets works similar to previews for SwiftUI. Create a struct, which is generally named “View_Previews” that conforms to PreviewProvider. PreviewProvider requires a static variable named previews of type some View. When using previews with widgets, use the .previewContext to set the size of the widget.
Waterlogged's widget only supports the .systemMedium size, and there are also .systemSmall and .systemLarge sizes currently. A Medium widget occupies the space of 8 app icons; the small occupies 4 app icons; and the large occupies 16 app icons worth of space.
Widgets are a brand new feature in iOS 14, and they open up the home screen to endless customization options. Developers now have the ability to provide users with relevant app information directly on the home screen. An excellent way to make your app stand out is by creating a beautiful widget that contributes to a rich user experience.