4️⃣Configuring Widgets

Fall 2023 | Reade Plunkett

AppIntents

AppIntents is the framework developed by Apple that allows us to configure our widgets with custom information provided by the user.

Creating a New Intent

We will create a new intent that allows the user to input a location and have the weather widget update its displayed conditions.

Let’s start by creating a new Swift file, which we will call LocationAppIntent.

Within that file, we will define a new structure that conforms to the WidgetConfigurationIntent protocol. This protocol provides us with an interface for configuring a WidgetKit widget.

LocationAppIntent.swift
import WidgetKit
import AppIntents

struct LocationAppIntent: WidgetConfigurationIntent {

}

This protocol requires us to implement a title for this Intent. Additionally, we can also implement a description on what this intent does.

LocationAppIntent.swift
static var title: LocalizedStringResource = "Location"
static var description = IntentDescription("Enter a location to view the weather.")

Next, we can define a parameter for this intent with a default value. The parameter will contain the location that use inputs.

LocationAppIntent.swift
@Parameter(title: "Location", default: "Ithaca, NY")
var location: String

Changes to WeatherEntry

Since our intent can make changes to a widget entry within our timeline, we must include it inside of the WeatherEntry model.

WeatherWidgetEntry.swift
import WidgetKit

struct WeatherEntry: TimelineEntry {
    let date: Date
    let weather: Weather
    let configuration: LocationAppIntent
}

Changes to WeatherTimelineProvider

We must also update our timeline provider to support this new app intent. We will change our provider to conform to the AppIntentTimelineProvider protocol to do so.

WeatherWidgetProvider.swift
struct Provider: AppIntentTimelineProvider {

}

Additionally, we will need to update the function headers for fetching a snapshot and timeline entry.

WeatherWidgetProvider.swift
func snapshot(for configuration: LocationAppIntent, in context: Context) async -> WeatherEntry {

}
WeatherWidgetProvider.swift
func timeline(for configuration: LocationAppIntent, in context: Context) async -> Timeline<WeatherEntry> {

}

These two functions provide us with the intent called configuration that contain user-customized values for the location.

Within both of these functions, we have to update our WeatherEntry instantiation to pass in the configuration.

WeatherWidgetProvider.swift
let entry = WeatherEntry(date: Date(), weather: possibleWeathers.randomElement()!, configuration: configuration) 

Additionally, we also have to update the entry created within our placeholder function with a new intent object.

WeatherWidgetProvider.swift
func placeholder(in context: Context) -> WeatherEntry {
    return WeatherEntry(date: Date(), weather: .sunny, configuration: LocationAppIntent())
}

Changes to Widget Configuration

Within WeatherWidget.swift, we also have to change our widget’s configuration from StaticConfiguration to AppIntentConfiguration.

WeatherWidget.swift
AppIntentConfiguration(kind: kind, intent: LocationAppIntent.self, provider: Provider()) { entry in
    WeatherWidgetView(entry: entry)
}

Changes to View

Finally, within WeatherWidgetView, we can update the “Ithaca, NY” string to display the location passed in through the app intent associated with the timeline entry being displayed.

WeatherWidgetView.swift
HStack {
    Text(entry.configuration.location)
        .font(.system(size: 18, weight: .semibold))
        .minimumScaleFactor(0.5)
    Spacer()
}

Additionally, to view the widget in the Preview window, we will need to provide it with a default configuration. We can define a default intent as an extension of our LocationAppIntent within our view. Since we do not want this default value being used anywhere outside this file, we mark it with fileprivate.

WeatherWidgetView.swift
extension LocationAppIntent {
    fileprivate static var cupertino: LocationAppIntent {
        let intent = LocationAppIntent()
        intent.location = "Cupertino, CA"
        return intent
    }
}

We then update the preview entries to use this new configuration.

WeatherWidgetView.swift
#Preview(as: .systemSmall) {
    WeatherWidget()
} timeline: {
    WeatherEntry(date: .now, weather: .sunny, configuration: .cupertino)
    WeatherEntry(date: .now, weather: .cloudy, configuration: .cupertino)
    WeatherEntry(date: .now, weather: .overcast, configuration: .cupertino)
    WeatherEntry(date: .now, weather: .rainy, configuration: .cupertino)
    WeatherEntry(date: .now, weather: .lightning, configuration: .cupertino)
    WeatherEntry(date: .now, weather: .snowy, configuration: .cupertino)
}

Last updated