Our code-along continues as we help our widget rewrite the future and travel into an alternate timeline. Continue where you left off from Part 1, or traverse time and space and begin with the Part 2 starter project to jump right into the action. Find out how you can integrate system intelligence into your widgets to help them dynamically change at different points during the day and surface the most relevant information. Explore core timeline concepts, support multiple widget families, and learn how to make your widget configurable.
Once you've helped your widget find its place in time, move on to the third and final part of the Widgets Code-along to discover advanced widget concepts and timelines.
Hi, I'm Izzy. I'm an engineer on the iOS System Experience team. Thanks for joining me. Let's take a look at today's agenda. We'll talk about widget families and what they are. If you built a watchOS complication before, this will sound familiar. We'll cover timelines and how to update them. We'll go over how to make a widget user configurable. We'll talk about how a widget can link to content in its app. And we'll update our widget to do all of this. It sounds like a lot, but the power of SwiftUI combined with WidgetKit really makes each step simple and straightforward. There are three families provided by WidgetKit, systemSmall, systemMedium, and systemLarge. If you've built a complication before, these are the same in concept as complication families. As we saw in Part 1, the timeline provider is the engine of the widget. We returned one entry from our provider... but we could have returned many. The big question is what happens at the end? How do we provide more entries? The answer is that we pick a TimelineReloadPolicy. The first one, atEnd, tells WidgetKit that when the last entry of a timeline is displayed, to start scheduling an update. When that update happens, our timeline method will be called again, allowing us to return more entries. Similarly, "after" tells WidgetKit to start scheduling an update at the date provided. Let's say we passed in an after date of 10:00 PM. Then, WidgetKit will schedule an update for 10:00 PM, regardless of how far in the future or past other entries in the timeline are. Our last option is "never." "Never" means that the system will not independently update a widget. It is up to us to explicitly tell the widget when to reload via the widget center API. Note that there may be many timelines active in the system at a time, each with its own reload policy. In order to provide the best user experience, and provide widgets updates as close to when the user will see them as possible, the system intelligently schedules updates. This means "atEnd" and "after" represent the earliest times a widget can be refreshed. Widgets can be configurable right on the Home Screen. WidgetKit configuration is driven by SiriKit, which means that just by having configuration, our widget is eligible for intelligent system behaviors. The core technology for configuration is INIntents, specifically custom intents. For a deep dive into these subjects, see the configuration and intelligence presentation from this year's WWDC. Widgets do not have animation or custom interactions, but we can deep link from our widget into our app. SystemSmall widgets are one large tap area, while systemMedium and systemLarge can use the new SwiftUI link API to create tappable zones within the widget. This session is a code-along. We're picking up from where we left off on the first widgets code-along session, so if you've been following along so far, you're in the right spot. If not, don't worry, you can pick up from the Part 2 target in the sample project.
Since we're covering a lot of subjects in this session, I've made a checklist. We're starting in Part 2, so let's jump in. In our previous session, we limited our widget to just systemSmall. I have an idea for how to update it to work in the medium size as well, so let's do that.
First, we need to update our supported families. So we need to support systemSmall, and systemMedium. Now, we need to know which family our view is being drawn in.
WidgetKit provides a widgetFamily environment value that we can use.
Now, we can just switch over that family and decide which view we want to return.
We can move our AvatarView.
So for systemSmall, we're returning our same AvatarView, and in our other case, we'll return something new.
I used a snippet here, but this is just the same AvatarView we were using before in an HStack with some text next to it, and that text is our character's bio. In order to add a little bit of visual interest, I've added a background color as well. So let's go ahead and do that for our systemSmall. I like using this trick where I embed in an HStack, but I don't actually want an HStack. I want a ZStack. So we can just use the embed in HStack shortcut and change that to a ZStack and add a background color the same way. And now that we have a background color, we need a foreground color as well.
So let's take a look at what that looks like in our preview. So notice in our preview, we're returning our AvatarView. But our entry actually returns two different views based on widgetFamily. So let's just return our entry view directly instead.
We'll want to update both our placeholder and our primary at the same time.
You'll notice that our PlaceholderView... is always returning an AvatarView like our preview was. Instead, we want it to return our EntryView the same way.
So let's look at that in our preview.
Great. Now we have a medium-size view, and our preview was automatically updated to reflect the new content. Let's Build and Run this, and see it in action. Here's our Emoji Rangers app that we love.
And now, when I go to the widget gallery, I can add a small or a medium widget to my Home Screen.
So that's families. Let's talk about timelines for a second. Like we talked about earlier, WidgetKit is trying to schedule all the widgets on the system at the same time. And we're sitting here asking to reload when we don't even need to. We know our character's healing over time. We even know exactly when they will be fully healed. There's a timer right there. What we can do is generate a full timeline out until the character's healed, and we can fill that timeline with more entries than would have been fetched otherwise. That sounds ideal, so let's do that.
I use a snippet, and I hope you're following along, so I'll cover each line while you type. First, we have our selectedCharacter, and we have the endDate we know when our character will be fully healed. We're going to provide updates on one-minute intervals, and we're going to start at the currentDate. We start with an empty array of entries. And while our currentDate hasn't yet reached our endDate, we create a new entry with the currentDate, step the currentDate forward by one minute, and add that entry. So now, we have a full timeline and WidgetKit won't attempt to reload us until that timeline is exhausted.
We talked about intelligent system behavior before. One thing that means is if our widget is in a stack, the system can intelligently rotate to it. We can give the system hints about times we think it should prioritize our widget using "relevance." You may have noticed relevance as an optional property on timeline entry already. Let's go ahead and add it.
Note, the range of relevance values is set by us. Since our character, being fully healed, is the most important state, we can just use the health level directly as our relevance. So we can pass our relevance to our sample entry here.
And now, we have a full timeline with relevance. And that's timelines.
Now, our widget is only showing Power Panda. But there are two other heroes. That's unfair because both of them saved the day as well. Let's make which hero the widget displays a configuration option. I've already added an intent definition to this project. But here's how you would do it fresh. You go to File, New File, Search for Intent.
And add a SiriKit Intent Definition file.
I've already added it, so I'm not going to do that here. But one thing we want to double-check is that our target membership includes both the widget and the app.
So the Intent Definition file has a few fields we need to fill out. Because widgets display information, our category is information view. We have a custom title and description. And we need to make sure that we check this box to let our intent be eligible for widgets. Our intent has one parameter named "hero." And that parameter is an enum. And we can set what values our enum provides in the enum editor. Here I've already added Panda and Egghead, but we also need to add Spouty. So now my intent definition is complete. How do we make our widget use our intent definition? Back in our widget...
we need to change our widget type... from being a static configuration to an intent configuration. This requires one extra argument. And I'm going to let Xcode tell me exactly what it is by building... ...and applying this fix-it.
And we can use our CharacterSelectionIntent.
And an intent configuration needs a matching intent timeline provider. So up at the top, we can change our timeline provider to be an intent timeline provider.
And an intent timeline provider passes one extra argument to its snapshot and timeline methods.
And that's the configuration. We added it to snapshot, and we also need to add it to timeline.
Now we just need a map from the configuration to a character.
Note that these enum values look the same, but one is from the intent definition, and one is our character detail definition. Now we just need to set the selected character for the character of our intent.
Now let's Build and Run it and see it in action.
Now when I add Emoji Rangers widget from the gallery, it defaults to Power Panda.
But when I long press on it, I have this new "Edit Widget" option. When I tap that, my widget flips around, and I can select between Power Panda, Egghead and Spouty. And I want Spouty on my Home Screen. So that's configuration.
So this is super cool, but I'm kind of expecting to jump directly into the characters' detail screen, now that I can pick exactly which character is showing, and doing that is super easy. We just add a widgetURL modifier onto our view.
And we do that for both our systemSmall and our systemMedium views.
Now, when I Build and run...
when I add my Emoji Ranger widget... when I tap on it, now I go directly into Power Panda. And if I switch my favorite Ranger...
I'll go directly into their information as well. Fantastic. So now, we've built a fully-featured widget that supports multiple families, has a full timeline, supports user configuration, and deep links into its host app. There's one more section of this Code-Along that I would love for you to join me on. For insight on how to approach widget design, see the "Designing Great Widgets" talk. And my teammate, Nils, is giving a talk about how to make the most of SwiftUI and widgets to really nail those designs. Thank you and have a great WWDC.
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.