Widgets are bite-sized pieces of information from your app that someone can choose to place on their home screen or Today view. Discover the process of building the views for a widget from scratch using SwiftUI. Brush up on the syntax that you'll need for widget-specific construction and learn how to incorporate those commands and customize your widget's interface for a great glanceable experience.
To learn more about widgets, be sure to check out "Meet WidgetKit" and "Widgets Code-along".
Hello and welcome to WWDC. Hello. My name is Nils and I'm an engineer on the iOS System Experience team. Today I'm going to show you how to build SwiftUI views for widgets.
First we'll talk about how widgets are enabled by SwiftUI. Then in the main part of this session, we're going to build the widgets view together and we'll finish by going over some of the new APIs that make building widgets with SwiftUI awesome. Let's jump right in.
With SwiftUI and WidgetKit, you can provide a timeline of views to be displayed by the homescreen at the appropriate time. Thanks to SwiftUI's adaptability, you can write a widget and deploy it to both iOS and MacOS. Because they're self-contained experiences new in iOS 14 and MacOS Big Sur, widgets are an amazing opportunity to learn and use SwiftUI, even if you need to deploy your app to older versions of our OS's, where SwiftUI may not be available.
I don't know about you, but I've been drinking a lot of coffee lately. Some days a bit too much. So I created an app to log and track the caffeinated beverages I drink, and to give me an estimate of how much caffeine is in my body. I called it wired.
I think a widget would be a great addition to the app, to be able to know at a glance, at all time how caffeinated I am. This is what I want my widget to look like.
First you can see that it's using my apps visual identity and color scheme.
At the top we are showing the amount of caffeine currently in my body, and at the bottom the last drink I had and how long ago that was. Note that the shape of the background for the caffeine amount is concentric with the widgets shape. Also I want the duration at the bottom of the widget to be updating live to always be correct. Let's jump to the demo to see how SwiftUI makes all of this easy.
I've already set up the widget configuration to return a timeline so that we can focus on creating the views for our widget here. I'm going to be using SwiftUI to build this widgets view, and that means that everything I write here can be used in your app too.
Here is my view, and here is the preview for it. I'm using a widget preview context to specify which widget family I want my preview to display. In this case, system small.
First let's define a struct for the data that is going to drive our UI.
I call it caffeineWidgetData and it has three properties for the caffeine amount, the drink name, and the drink date. I also have a static constant for some preview values that I'm going to use to make my previews look real.
Let's add the property to our view to hold this data. Next let's update our preview to use the preview data. We're ready to start building our view. Let's start by adding some content.
I'm using a VStack to stack views vertically. At the top, I have the word caffeine styled with a body font bold, and using a custom color that is defined here in my asset catalog.
Below that I have the amount that gets a bigger font but a similar look.
Because this font is pretty big, I want to allow it to shrink a bit if our layout requires it. That's why I have set a minimum scale factor here.
Next let's give our widget some personality by putting a background color behind it using a ZStack. A ZStack allows you to overlay views on top of each other. You notice that our widget automatically gets its shape, size, and corner radius from the system.
Now let's continue building our widget by adding the name of the last drink I had and how long ago that was. For the time elapsed since my last drink you can see that I am using a new initializer on text that takes a date and a style to format it for you. When using this initializer, the contents will count up or down from the specified date automatically as time passes. This is a great way to make your widgets feel alive on the home screen. Let's see what it looks like by clicking the live preview button in the canvas. You can see that the duration is counting up. I can even use string interpolation to add more content to the string so I can add the word ago, just like this.
And because this is using string interpolation the framework will generate a localized key for you so that the format can be adapted for other locales and languages. We really have two groups of content here as shown by the text color. This brown text at the top and this white text below it. So let's split them into two VStacks, and add a spacer between them.
I'm going to comment click on this text here and select embedding VStack.
Gonna move this text inside the VStack. Next, I'm going to do the same thing with this text here. Comment click, Embed in VStack. And move the second text inside. And add a spacer between them.
I'm going to make both of my VStacks leading aligned. To do that using the canvas, I can click the leading button in the vertical stack section of the attributes inspector. This is starting to get a bit complicated, so I'm going to extract the top and bottom part into their own subviews. Because this is SwiftUI views are cheap and I can do this with almost no cost.
I come and click on the VStack here and select ExtractedView. Let's call it caffeineAmountView. Let's add a property to it to hold some data.
Then I'm going to do the same thing to the bottom VStack. Comment click, ExtractedView. Let's call it DrinkView. And let's also add the same property to it.
Finally let's pass in some data.
Things look the same but we have better code structure to build upon. I want my content to be leading line, instead of centered. I'm going to wrap the content of the caffeine amount view in an HStack and add a spacer at the end. Comment click, and add an HStack.
An HStack, or horizontal stack, allows you to place views next to each other. By putting a spacer at the end, I'm telling the layout system to push my content to the opposite edge, the leading edge. Because I don't need any minimum spacing, I'm going to specify a mean length of zero so that my content can take as much space as is available. Let's add some padding to our content to make it look better. Because this is SwiftUI, I can do this using the canvas, by placing my cursor on the main VStack. And then clicking the four checkboxes here in the padding section of the attributes inspector. Easy.
You can see that I'm not specifying a value for that padding. SwiftUI will use an appropriate default value for the device and configuration it's running on. I want to emphasize the caffeine amount view, so I'm going to give it a background. I'm using the color named the latte from my asset catalog. Let's add some padding again by clicking the four checkboxes in the attributes Inspector.
That's a bit too much. Let's make it eight.
You can see that the corners of this shape are not rounded. As described in the human interface guidelines for widgets, rounded rectangles are an important part of widgets visual design language. So I'm going to round the corners of this shape. I could use a corner radius modifier like this.
And then try to find a value that looks good.
But then different devices may use a different radius for their widgets so it can become a bit cumbersome. We have a much better way to do this In iOS 14, using the new ContainerRelativeShape. ContainerRelativeShape is a new shape type that will take on the path of the nearest container shape specified by a parent with an appropriate corner radius based on the position of the shape. In this instance, the system is defining the container shape for a widget and we get a corner radius concentric with it. Super easy. If I change the value for the padding of our main VStack, the corner radius of the caffeine amount view changes so that the border around it keeps a constant thickness around the corners curve. Finally, let's give our most important content some room to breathe using a spacer. There, we've implemented the design that we were going for. I personally think it looks gorgeous and I can't wait to add it to my home screen. Let's see what my widget looks like in Dark Mode. To do this, let me duplicate my preview and other modifier to set it to Dark Mode.
Because all of my colors come from an asset catalog where I've already defined dark appearance variance for them, my widget automatically adapts for Dark Mode. My mom drinks a lot of coffee too. So I'm sure she will be super excited about Wired, but this small text at the bottom may be difficult for her to read. So let's update our widget to support dynamic type so that she and all our users who prefer a larger text can use it too. First let's add a new preview to check what my widget looks like at a larger size category.
Here I set the size Category to Extra, Extra, Extra Large. It looks great thanks to SwiftUI's adaptive nature, I didn't have to change anything to super dynamic type, and I've built a widget that's more inclusive with no additional work. Isn't that amazing. Next I want to talk about place holders. For a great widget experience, you should provide a place holder that the system can use while it's requesting of you from your extension or while the device is locked. You place holder should look just like your widget but without any specific content. Let's see how we can modify our view to also use it as a place holder. First I'm going to create another preview by copying an existing one. And I'm going to isPlaceholder true modifier to it to make it a place holder. You can see that SwiftUI knows to automatically replace the content of all my text with rounded rectangles and to style them in a way that matches the content they're replacing. And if I want to preserve some text in my placeholder I can add the isPlaceholder false modifier To it. Let's add it to the word caffeine.
Just like that, I updated my view to use it as the place holder for my widget. Simple right. The isPlaceholder modifier that I just showed you will be available in a later seed. You probably know that widgets come in three different families: system small, medium, and large. Let's look at how easy it is to update my widget for another widget family. Let's start by adding another preview versus the medium. This looks okay but it's a bit of a waste of space. In my app I let the user take a photo of their drink when they log it. So let's make better use of screen real estate by including these photos in our widget. First I'm going to add the property to my data set to hold the name of that photo. And then add a sample photo from a preview asset catalog. Let's add the photo to our view.
We only want the drinks photo to appear on system medium. To be able to create this conditional layout, let us add an environment variable for the widget family. I want my photo to appear next to the existing content so I'm going to wrap all our content in an HStack. Then if we are in a system medium widget, and if we have a photo to display, let's add our image.
And make it resizable.
Finally let's look at our place holder for this size by adding another preview. I'm pretty sure you're starting to get the hang of this. I'm copying the existing one and adding a modifier to it to make it a place holder. You can see that SwiftUI knows to automatically replace the content of my image with a fill color so I didn't have to do any additional work to create my system medium placeholder. Awesome right. And that's how easy it is to build your widgets views with SwiftUI.
Let me go over two new API's that I just showed you in the demo. First let's look at how to make a corner radii look beautiful in your widget. When nesting rounded racks, most of the time you don't want them to use the same corner radius. Instead you want them to be concentric. In iOS 14, this is effortless. Set the new ContainerRelativeShape as the background for your view and SwiftUI will take it from there. That's almost too easy. In iOS 14, we added a new initializer on text to format a date according to a style. This allows you to create countdown's, timers, and other styles of absolute and relative dates. Because this will automatically update as time passes, it's a great way to make your widgets feel alive on the home screen.
Let's wrap it up. SwiftUI enables you to build compelling widget experiences for your users. Widgets leverage SwiftUI's existing support for adaptive layouts.
We've added new API's is to make it easier to do some things that we found common in widgets. Note that these API's work everywhere, not just in widgets. So use them in your apps too.
Be sure to check out the Meet WidgetKit session to learn more about widget timelines and also the Widgets Code-along by my colleague, Izzy to watch him use WidgetKit to build a widget from scratch. I hope you
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.