Adding Configuration Options to a WidgetKit Widget

The WidgetDemo app created in the preceding chapters is currently only able to display weather information for a single geographical location. Through the use of configuration intents, it is possible to make aspects of the widget user configurable. In this chapter we will enhance the widget extension so that the user can choose to view the weather for different cities. This will involve some minor changes to the weather data, the modification of the SiriKit intent definition and updates to the widget implementation.

Modifying the Weather Data

Before adding configuration support to the widget, an additional structure needs to be added to the widget data to provide a way to associate cities with weather timelines. Add this structure by modifying the WeatherData. swift file as follows:

import Foundation
import WidgetKit
 
struct LocationData: Identifiable {
    
    let city: String
    let timeline: [WeatherEntry]
    
    var id: String {
        city
    }
    
    static let london = LocationData(city: "London", 
                                 timeline: londonTimeline)
    static let miami = LocationData(city: "Miami", 
                                timeline: miamiTimeline)
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(city)
    }
}
.
.

Configuring the Intent Definition

The next step is to configure the intent definition which will be used to present the user with widget configuration choices. When the WeatherWidget extension was added to the project, the “Include Configuration Intent” option was enabled, causing Xcode to generate a definition file named WeatherWidget.intentdefinition located in the WeatherWidget project folder. Select this file to load it into the intent definition editor where it will appear as shown in Figure 51-1:

Figure 51-1

Begin by making sure that the Configuration intent (marked A in Figure 51-1 above) is selected. This is the intent that was created by Xcode and will be referenced as ConfigurationIntent in the WeatherWidget.swift file. Additional intents may be added to the definition by clicking on the ‘+’ button (D) and selecting New Intent from the menu.

The Category menu (B) must be set to View to allow the intent to display a dialog to the user containing the widget configuration options. Also ensure that the Intent is eligible for widgets option (B) is enabled.

Before we add a parameter to the intent, an enumeration needs to be added to the definition file to contain the available city names. Add this now by clicking on the ‘+’ button (D) and selecting the New Enum option from the menu.

After the enumeration has been added, change both the enumeration name and Display Name to Locations as highlighted in Figure 51-2 below:

Figure 51-2

With the Locations entry selected, refer to the main editor panel and click on the ‘+’ button beneath the Cases section to add a new value. Change the new case entry name to londonUK and, in the settings area, change the display name to London so that the settings resemble Figure 51-3:

Figure 51-3

Repeat the above steps to add an additional cased named miamiFL with the display name set to Miami.

In the left-hand panel, select the Configuration option located under the Custom Intents heading. In the custom intent panel, locate the Parameters section and click on the ‘+’ button highlighted in Figure 51-4 to add a new parameter:

Figure 51-4

Name the parameter locations and change the Display Name setting to Locations. From the Type menu select Locations listed under Enums as shown in Figure 51-5 (note that this is not the same as the Location entry listed under System Types):

Figure 51-5

Once completed, the parameter settings should match those shown in Figure 51-6 below:

Figure 51-6

Modifying the Widget

With the intent configured, all that remains is to adapt the widget so that it responds to location configuration changes made by the user. When WidgetKit requests a timeline from the provider it will pass to the getTimeline() method a ConfigurationIntent object containing the current configuration settings from the intent. To return the timeline for the currently selected city, the getTimeline() method needs to be modified to extract the location from the intent and use it to return the matching timeline.

Edit the WeatherWidget.swift file, locate the getTimeline() method within the provider declaration and modify it so that it reads as follows:

func getTimeline(for configuration: ConfigurationIntent, in context: Context, 
              completion: @escaping (Timeline<Entry>) -> ()) {
    
    var chosenLocation: LocationData
        
    if configuration.locations == .londonUK {
        chosenLocation = .london
    } else {
        chosenLocation = .miami
    }
 
    var entries: [WeatherEntry] = []
    var currentDate = Date()
    let halfMinute: TimeInterval = 30
 
    for var entry in chosenLocation.timeline {
        entry.date = currentDate
        currentDate += halfMinute
        entries.append(entry)
    }
    let timeline = Timeline(entries: entries, policy: .never)
    completion(timeline)
}

In the above code, if the intent object passed to the method has London set as the location, then the london entry within the LocationData instance is used to provide the timeline for WidgetKit. If any of the above changes result in syntax errors within the editor try rebuilding the project to trigger the generation of the files associated with the intent definition file.

Testing Widget Configuration

Run the widget extension on a device or simulator and wait for it to load. Once it is running, perform a long press on the widget to display the menu shown in Figure 51-7 below:

Figure 51-7

Select the Edit Widget menu option to display the configuration intent dialog as shown in Figure 51-8:

Figure 51-8

Select the Miami location before tapping on any screen area outside of the dialog. On returning to the home screen, the widget should now be displaying entries from the Miami timeline.

Note that the intent does all of the work involved in presenting the user with the configuration options, automatically adjusting to reflect the type and quantity of options available. If more cities are included in the enumeration, for example, the intent will provide a Choose button which, when tapped, will display a scrollable list of cities from which to choose:

Figure 51-9

Customizing the Configuration Intent UI

The final task in this tutorial is to change the accent colors of the intent UI to match those used by the widget. Since we already have the widget background color declared in the widget extension’s Assets.xcassets file from the steps in an earlier chapter, this can be used for the background of the intent UI.

The color settings for the intent UI are located in the build settings screen for the widget extension. To find these settings, select the WidgetDemo entry located at the top of the project navigator panel (marked A in Figure 5110 below), followed by the WeatherWidgetExtension entry (B) in the Targets list:

Figure 51-10

In the toolbar, select Build Settings (C), then the Basic filter option (D) before scrolling down to the Asset Catalog Compiler – Options section (E).

Click on the WidgetBackground value (F) and change it to weatherBackgroundColor. If required, the foreground color used within the intent UI is defined by the Global Accent Color Name value. Note that these values must be named colors declared within the Assets.xcassets file.

Test the widget to verify that the intent UI now uses the widget background color:

Figure 51-11

Summary

When a widget is constructed using the intent configuration type (as opposed to static configuration), configuration options can be made available to the user by setting up intents and parameters within the SiriKit intent definition file. Each time the provider getTimeline() method is called, WidgetKit passes it a copy of the configuration intent object, the parameters of which can be inspected and used to tailor the resulting timeline to match the user’s preferences.