Add configuration and intelligence to your widgets
Widgets are at their best when they show up on someone's Home screen or in the Today View at the right time and provide actionable, relevant information. We'll show you how to build configurable widgets to let people create a personalized Home screen experience, and that take advantage of system intelligence to help people get what they want, when they want it. Learn how to customize your widget's configuration interface, and how to appear within Siri Suggestions or at the top of a widget Smart Stack based on user behavior or by letting the system know when there's new, timely information.
For more on widgets, watch "Meet WidgetKit". And for more on system intelligence, watch the collection "Design for Intelligence."
Welcome to "Add Configuration and Intelligence to Your Widgets." My name is Ian, and I'm here with my colleague Kelvin. In this talk, we'll show you how to make widgets configurable, and show you how a configurable widget can help the system work in more intelligent ways. New in iOS 14 and macOS Big Sur, we have a whole new widget experience. Widgets allow your app to surface new information to people in exciting new ways. Widgets can live in more places than ever. They can live on pages of the home screen, as part of stacks, on the today view, and in macOS Notification Center.
Powering those widgets is WidgetKit. WidgetKit allows you to create multiple widgets, each to solve its own unique problem. Each widget can support different sizes and layouts. Let's take a look at an example. We've built an example app that lets you view all of the transactions on your credit cards. It lets you view information about your spending, and track balances on all of your accounts. The two most common things that I do in the app are check balances and look at recent transactions. It would be really great to see this information right at a glance on the home screen. So we've built two widgets for our app. The first one is called Recent Purchases, which shows a list of purchases on one of your cards. The other one is called Due Date, which shows your next payment's due date, and how much you owe.
Because I often want to see information about one card at a time, we added configuration to pick which card to show. I can change which card is shown by tapping on an Edit Widget and then selecting a card. Because we made our widget configurable, now people can create multiple different instances of the due date widget, and show a different card in each. Now let's look at what we're going to cover today. First, we'll talk about the basics of how to add configuration to your widget. Next, we'll talk about the types of information that you can ask the user to enter, and show how to populate the interface with data from your app. After that, we'll talk about how you can customize the title, description, and colors of the backside of your widget.
And finally, we'll cover how adding configuration to your widget allows the system to better predict times to display your widget in a stack. Let's start with the basics. The first piece of the system is the widget extension.
Widget extensions contain SwiftUI code that renders the visual appearance of the widget. They also contain some metadata about the widget itself.
Before you get started, make sure that you have set up a widget extension already using WidgetKit and SwiftUI. If you haven't done that yet, make sure to go watch the "Meet WidgetKit" talk. When your widget is configurable, you can specify which options to ask the user on the backside of the widget, and the system will display them for you. These questions are called parameters.
Here's our Recent Purchases widget. It has a Card parameter and a Category parameter.
The Card parameter allows people to choose a card to display, and the Category parameter lets people filter the list to only include transactions for a specific category, like Groceries, if they'd like. To define which parameters are shown, we use Intents, which is the same system that you can use to add support for Siri and Shortcuts to your app. An Intent contains an ordered list of parameters. Each parameter that you put in the Intent will show up as a row in the widget's configuration UI. You declare your Intents in Xcode using an Intents definition file. This file contains all of your Intents, their parameters, and indicates which parts of the system they support.
By compiling this file into your app, the system will be able to read information about your Intents. Once you define an Intent, Xcode will also generate an Intent class for you, containing a property for each parameter. For this widget, we have a ViewRecentPurchasesIntent class, with card and category properties.
An instance of this Intent class will be passed into the widget extension at runtime, allowing your widget to know what the user has configured and what to display. Next, let's talk about the types of parameters that you can add to your Intent. As I mentioned before, we build the widget configuration UI for you, based on your Intent's definition. We support a wide variety of input types, which I'll walk you through now. When you specify String as the type of your parameter, the configuration UI shows a text field, and it will show a switch for the boolean parameter. The configuration UI also supports numbers, with a few different input mechanisms. For integers, it supports number fields and steppers. And for decimals, it supports number fields and sliders. The configuration UI also has support for contact and location pickers.
The Person type will show as a contact picker, and the Location type will show a location picker. The configuration UI also supports enumerations. You can make these enumerations static, or dynamically populate them with content from your app. We'll talk about dynamic options in more detail later.
The configuration UI supports a number of other types as well, each with their own UI. Parameters can also support multiple values. For example, if your widget shows a list of events from your calendars, you can configure the Intent to support selecting multiple calendars. New in iOS 14, Intents now supports fixed sized arrays. This is useful if your widget supports showing a fixed number of items, and lets you prevent the user from adding more than a defined number of items into the array. You can specify the number of items based on the widget size in the Intent Editor. Now, I'm going to walk you through how to add rich configuration to your widget.
The first step is identifying what you want to make configurable. Let's use the Recent Purchases widget here as an example. In the Recent Purchases widget, each widget can only show one card but people may have multiple cards in the app, so it makes sense to make that configurable. This allows people to have multiple Recent Purchases widgets on their home screen, each showing different cards. It also would be useful to be able to filter by category. That way, people could have one widget to show transportation expenses, and another widget to show food expenses. So, in addition to making the card configurable, we are also going to allow people to configure the category.
Now, I'm going to walk you through how we can make the Recent Purchases widget configurable. This is our project, where we already have our widget setup.
You can see that the widget currently has a Static Configuration.
In order to make the widget configurable, we need to switch from a Static Configuration to an Intent Configuration.
The first step in switching to an Intent Configuration is defining an Intent that you will use to configure the widget. You can define a custom Intent inside of an Intent definition file.
I already have one here in this project, and you'll note that it is checked in to both the app and the widget extension targets. Now, let's create an Intent using the plus button in the bottom left.
I'll be calling it ViewRecentPurchases.
Next, we're going to check the "Intent is eligible for widgets" option. This will allow the Intent to work as a configuration for a widget. If you want this Intent to also support the Shortcuts app, check the "Intent is user-configurable in the Shortcuts app and Add to Siri" option. We'll uncheck this option for this demo and just focus on widgets. We are also going to leave the "Intent is eligible for Siri Suggestions" checkbox unchecked for now, and my colleague Kelvin will walk you through how to add intelligence to your widget later in this talk. We'll also set the category of this Intent to "View" because the purpose is to view your transactions. Now, using the plus button in the parameters list, I am going to add a parameter for the card.
The first step here is naming the parameter. This label will be displayed in the Widget Configuration UI. I'm going to name this "Card." Next, I need to pick a type. Because a credit card represents an object inside of my app, I am going to create a custom type to represent it. You'll see that I already created a "Card" type, which has a name and identifier, so I'll use that here for my type.
The list of cards will need to be provided by the app and will change depending on who is signed in, so I will check the "Options are provided dynamically" checkbox.
We'll go into detail later on how to populate this dynamic list. Since this Intent won't support running in Siri, we can uncheck the "Siri can ask for value when run" option.
Next, because we want to allow people to choose the category, we will add a category parameter to the Intent.
We can use an enum for the type of this parameter because there are a fixed set of categories. I already have an enum defined, so I will choose that.
We'll set the default value for the category to "All" so that people will see all of the transactions by default.
And we'll also uncheck the "Siri can ask for value when run" option for the category.
Now, let's talk about dynamic options and search. In many cases, the data that you want to display in the widget configuration can vary by person, and can't be specified upfront in the Intent definition file. For example, the list of cards in our example app varies by person. You can accomplish this by implementing dynamic options. You can enable dynamic options for any parameter on an Intent, by checking the "Dynamic Options" checkbox in Xcode. Enabling dynamic options will indicate to the system that it should consult your app to retrieve the possible values that a person can choose from, instead of allowing them to enter any value that they want. Once you enable dynamic options, two things will happen: First, the visual appearance of your parameter will change on the back side of the widget, into a button that opens a modal list of options. Second, two methods will be generated for you to implement in your app, one to provide a list of possible options and a default value. These methods are part of the Intent handler protocol, which Xcode generates for you. You will need to make a class that conforms to this protocol, either in your app or an Intents extension. This class will be asked by the system to provide possible options while a person is configuring your widget. In the example app, the two methods that we need to implement are provide card options collection and default card.
For provide card options collection, we will load the list of cards that the user has set up and then call the completion handler. For default card, we return the primary card that the user has set up. The provide card options collection method lets you either provide a flat list of cards, or list of sections containing cards. For example, we can display a section for each type of card, like credit or debit. If you use sections, your UI will look like this. Now all of my credit cards show under the Credit section, and I can see more sections containing other types of cards. By default the search bar at the top will filter the options that you provide. In some cases your app might have more data than you can easily provide upfront in a list. If that's the case, you can provide search results as the person types. These results can include options outside of the standard options that you provide dynamically. To do this, make sure to check the "intent handler provides search results as the user types" checkbox, and also provide a prompt. If you check the checkbox, the provide card options collection method will get a search term parameter. When a person first looks at the list, this method will get called with a nil search term. And then as they start typing, the method will get called again with the updated search term.
Now, let's do a quick demo of how you can implement dynamic options. Continuing from our last demo, let's go ahead and implement returning the list of cards to the user dynamically. The first step is setting up the Intent handler for the ViewRecentPurchases Intent. We are going to put our Intent handler inside of an Intents extension. I already have an Intents Extension set up for our other widget, so I can add my code there. The next step is making our Intent handler conform to the ViewRecentPurchasesIntentHandling protocol.
You can see that this protocol has a single required method, which is to provideCardOptionsCollection method. We need to implement this such that it returns a list of cards inside of the app. First, I create custom Card objects from the objects inside of my app. Then, I put them into an InObjectCollection, which allows me to add sections if I want. I don't have any sections in mind here, so I will just create the collection from a list of items. And then call the completion handler with the created collection.
There is also an optional method to provide a default card.
The default is used when a widget is dragged onto the home screen for the first time, so it's really important to provide a good default.
Now that we've created our Intent and implemented our Intent handler, we can adopt the IntentConfiguration in our widget.
It's very similar to the Static Configuration that we had before, but it takes an Intent type as an argument. You'll also need to switch your timeline provider to be an IntentTimelineProvider. It is very similar to the default TimelineProvider, but the methods have an additional Intent argument that you use to determine what to show.
Here, we'll need to take the card from the Intent and use that to determine which purchases to show. Previously, it was just showing the purchases from the default card.
Let's run the code and try to configure the widget.
Notice that when I drag out the widget, it shows me transactions from the default card.
This is because I implemented the default card method. Now, if I go and flip it around, and tap on the card button, it shows me a list of cards.
Tapping on the category option also shows a list of categories. Voilà.
Now let's talk about a few ways that you can further customize the appearance of your widget configuration UI. You can customize the title and the description by using the SwiftUI modifiers called "configuration display name" and "description" in your widget extension. Next is colors.
You can style the background and the accent color of the Widget configuration UI to match your app's color scheme. To do this, you'll need to add named colors to your widget extension's asset catalog. Here, we're adding one for the accent color and another one for the background color.
And then you'll need to add the names of your colors to your widget extension's build settings for Global Accent Color Name and Widget Background Color Name.
Another thing you might want to do is hide and show specific parameters based on another parameter. For example, in the Calendar widget, if you turn off the Mirror Calendar App switch, the calendar parameter appears, and you can manually choose which calendar to show. Let's take a look at the Intent definition file to see how to specify the relationship between "mirror calendar app" and "calendar". To specify that we only want the calendar parameter to show up when the mirror calendar app option is off, make the MirrorCalendarApp parameter a parent of the calendar parameter. To do that, we'll select the calendar parameter, then select mirror calendar app as its parent.
Then, we'll set it up to show only if the parent has the exact value of false.
Now, the calendar parameter will only show up if the mirror calendar app is turned off. That's it from me! Now over to Kelvin to talk about system intelligence and widgets.
Hi, I'm Kelvin, and I work on the Proactive team here at Apple. Next, we're going to talk about system intelligence. Widgets are one of the most exciting new opportunities across iOS, iPadOS and macOS. On iOS 14, not only can you add widgets to the home screen, you can stack multiple widgets together and provide access to a variety of widgets in one place by easily swiping through.
Everyone will find a different way to take advantage of this, but it's easy to imagine that you could group multiple widgets together, that would be useful as you progressed through your day. Now what's exciting about stacks, is that the system can automatically rotate widgets to the top of the stack to provide useful and timely information. So let me walk you through the underlying design principles that drive stack intelligence, and how you should implement some new APIs to ensure your app is part of this new home screen experience. Let's dig a little deeper into what makes for good smart stack. Stacks should provide timely and glanceable information with obvious value to the user. You want your widget to be surfaced when you have timely information relevant to the people using your app. Like when you know there is a thunderstorm coming, but not when you're just updating the temperature regularly. Now broadly speaking, the system will try to surface widgets based on two reasons. The first is user behavior based.
We want to surface widgets that provide information the user typically looks for at a particular time. For example, if someone frequently launches your weather app to check on the weather, the system can instead surface your widget with that information to provide quicker access to what they're looking for. The second is based on relevant information from your app.
So for example, if a thunderstorm is happening, your widget can inform the system that has a highly relevant update, and the system will consider surfacing your widget to the top of the stack. Now, let's talk about the APIs you can use to provide the information the system needs to surface your widget.
Let's start with user behavior. So the system wants to surface your widget when someone is typically looking in your app for information. In iOS 12, we introduced the concept of Shortcuts and custom Intent donations, which provides a way for the system to understand what people do in your app.
Using this information, the system provides predictions in Spotlight on what actions they want to perform. Now new in iOS 14, these same donations will also inform the system about when would be a good time to surface your widget. So now, let's continue from our previous example with the Recent Purchases widget. Here we have already set up an Intent for widget configuration.
It allows a user to specify the credit card and a category they want to view.
Now to enable system intelligence to rotate your widget, we have to set the same Intent for donation by our host app. The idea here is we want to inform the system when a user is checking a particular credit card in our app and we will do so by donating this Intent. We'll describe what happens under the hood in just a bit. So for now let's dive into some details about how to set it up. First we need to mark this Intent as eligible for Siri suggestions and that opens up the bottom section here labeled Suggestions.
And for our example we want the system to predict when someone would check a particular credit card and we wanted to match any widget with that same card configured. In order to convey that we need to add a supported combinations with just the card parameter. So now that we have set up our custom view recent purchases Intent we need to have our host app donate it whenever the user views recent purchases in our host app. And to do that we create an INInteraction object and call the donate function, passing in our Intent.
And since we've specified a supported combinations as just a card parameter even if we provided the category parameter in our donation the system will only consider the card parameter. And that's all you have to do. The system will do the rest. Now let's take a closer look at what the system does with your donations under the hood. So let's say someone views grocery purchases for our Acme card regularly at noon and checks their SoupPay card for restaurant purchases in the evenings. Now our host app would donate Intents like this. And based on these donations the system will recognize that the user typically checks their Acme card at noon and then on Friday at noon the system would predict Intent for viewing Acme Card and would surface our widget as long as they have configured it to show their Acme card regardless of the category. But what if instead we added both card and category as a supported combination. Let's look at what would happen.
Now since category is part of the supported combination you are telling the system that is an important parameter. Then on Friday at noon the system would predict Intent for viewing Acme card in the groceries category which would only surface widgets where the user has explicitly configured both parameters to show the card for the groceries category. So in short supported combinations is the way to communicate to the system which configurable parameter truly represents the information people are looking for. Now we walk through a lot of steps there. So to quickly summarize. First set up a configuration for your widget with an Intent. Second mark it as eligible for a Siri suggestions to allow the system to predict it. Next configure supported combinations for only the parameters that you want the system to predict. And finally donate the Intent when the user views that information in your host app. Now let's talk about the second scenario where you want the system to surface your widget, when you have important relevant information.
So a quick recap, using WidgetKit you provide timeline entries that would determine what your widget will look like at various points in time. It works great whether your widget can schedule your entries ahead of time like this weather example where you can provide forecasts for future views.
It also works if your widget needs to react to new information in real time. Like our recent purchases example where you can only provide each entry as they occur. So now let's say we know that people using our app wants to be alerted if they got a charge for over $50. How can you inform the system that your widget has relevant information for the Acme books purchase? You can convey that information by supplying a timeline entry relevance object with your timeline entry. A timeline entry has effectively three components. A date timestamp that determines when this entry should be rendered, the view that should be rendered, and the relevance of this entry. The relevance is a timeline entry relevance object which has two fields a score and a duration.
And here's what it looks like in Swift. Let's start with the score. The score is a value indicating how relevant an entry is compared to all entries provided in the past. The range and scale is largely up to you to define as the system only considers the score in relation to other entries. The only exception being a score of zero or lower will indicate to the system that your widget currently has no relevant information and should not be surfaced.
Now coming back to our example we want to convey to the system that we have important information when there is a purchase of more than fifty dollars.
We can simply provide a score of 1 when that happens to emphasize importance and a score of zero point one for all other purchases. That way there will be a slight chance the system might surface our widget for the minor purchases but we will try to prioritize for the big purchase. When there is no important information like when we have no recent purchases at 8:04AM, we set a score of zero. And keep in mind it doesn't matter what scores other widgets provide. Scores are only compared within the scores you provide. Now on the other hand, let's say we think the amount spent is a good indicator of relevance. We can set the amount spent to be the score and the system will likewise prioritize surfacing the expensive purchases. Next let's talk about duration. The duration is a field for situations where you have a well defined period of time where the relevant score will be fixed. Otherwise feel free to leave the duration at zero which will mean the relevant score will last until the next timeline entry relevance is received. Here's an example to highlight how duration can be used. We want to build a basketball widget that shows the progress of a game as it occurs. So at the beginning of the game we can create an entry with a score of 1 and a fixed duration for the length of the game. During the game, we can continue to make timeline entry updates without affecting their relevance by just leaving the timeline entry relevance field as nil. By setting the entry as nil is how you can tell the system to ignore this update for relevance purposes. So to wrap up.
With smart stacks, we have an opportunity to surface your widget on top of the stack. You can make this work for your widgets in two ways. First by donating Intents from your app that match configuration of your widget and secondly by providing timeline entry relevance when your widget has important information. Thanks for watching. We can't wait to see what kind
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.