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.
♪ Voiceover: Hello, and welcome to WWDC. Izzy Fraimow: 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've 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 timeline reload policy. 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 pm. Then WidgetKit will schedule an update for 10 p.m. 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 on 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 'INIntent,' 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 in 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 for us. 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 or view is being drawn in. WidgetKit provides a widget family environment value that we can use.
Now, we can just switch over that family and decide which view we want to return.
Can move our avatar view.
So for 'systemSmall,' we're returning our same avatar view, and in our other case, we'll return something new. I used a snippet here, but this is just the same avatar view we were using before, in an H-Stack 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 an 'HStack,' but I don't actually want an 'HStack.' I want a 'ZStack.' So we can just use the embed an '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 avatar view, but our entry actually returns two different views based on widget family. So let's just return our 'EntryView' directly instead.
We'll want to update both our placeholder and our primary at the same time.
You'll notice that our placeholder view 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-sized 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 characters 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 is 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 used a snippet, and I hope you're following along, so I'll cover each line while you type. First, we have our selected character, and we have the end date 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 current date. We start with an empty array of entries, and while our current date hasn't yet reached our end date, we create a new entry with the current date, step the current date 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 'TimelineEntry' 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. 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 save 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 or category as InformationView, 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 edited 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. I'm going to let Xcode tell me exactly what it is, by building and applying this FixIt. And we can use our character selection intent. And an Intent Configuration needs a matching 'IntentTimelineProvider.' So up at the top we can change our 'TimelineProvider' to be an 'IntentTimelineProvider,' and an 'IntentTimelineProvider' passes one extra argument to its snapshot and timeline methods. And that's the configuration. We've added it to 'Snapshot,' and we also need to add it to 'Timeline.' Now we just need to 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 ned 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, and 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 widget URL 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 'Design Great Widgets' talk. And my teammate Nils is giving a talk about how to make the most of SwiftUI in 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.