SwiftUI & UI Frameworks • iOS, macOS, visionOS, watchOS • 24:28
WidgetKit elevates your app with updates to widgets, Live Activities, and controls. Learn how to bring your widgets to visionOS, take them on the road with CarPlay, and make them look their best with accented rendering modes. Plus, find out how relevant widgets can be surfaced in the Smart Stack on watchOS, and discover how push notifications can be used to keep your widgets up to date.
Speaker: Tanner Oakes
Downloads from Apple
Transcript
Introduction
Hi, my name is Tanner Oakes, and I’m an engineer on the System Experience team. Widgets are a great way to surface timely information and actions across the system, keeping your app useful even when it’s not front and center. WidgetKit continues to expand, making your widgets more capable and taking your widgets to new places.
My friend Luca has a caffeine logging app. I've been helping him update it. Throughout this talk, I’ll show you how it takes advantage of everything new in widgets. In this video, I’ll cover widgets, Live Activities, and controls in new places across our platforms. I’ll also cover a new way to show relevant widget content in the Smart Stack on watchOS. And finally, I’ll show you how to keep your widgets up to date across devices with push notifications. Widgets, Live Activities, and controls are in a lot of new places.
Widgets in new places
But first, I’ll cover a new look for widgets in the places where they first got their start. In iOS 26, the Home Screen can be configured to present icons and widgets in a clear glass presentation, or further customized by selecting a specific tint color, like blue here. These looks can be configured for widgets on the desktop and Notification Center in macOS Tahoe as well.
These new presentations are constructed similarly across iOS and macOS. First, the widgets content is generated using accented rendering mode, tinting all of the content to white. Then the widgets background is removed and replaced with a themed glass or tinted color effect. My caffeine tracker widget looks great in accented mode, without having to do anything.
Some widgets might need some tweaking to look their best in accented mode. I’ve added a widget to my app that shows me my most frequently consumed beverage. For me, it's a matcha latte. My widget has a big image representing the beverage, a title at the bottom, and a gradient behind the text, making it more readable when placed on top of the image.
When accented rendering is applied, all of the widget’s content is tinted white. For opaque content like my latte cup, it is all a single white color. Partially transparent content like my gradient is tinted white while maintaining its opacity. This is really not the look I'm going for. I can’t make out the image anymore and text is hard to read. I’ll show you how I can update my widget to better handle accented rendering.
Here is my widget’s view. I have a ZStack with the beverage image, the gradient, and then the text view. First, I’ll add the widgetRenderingMode environment variable to my widget view. I’ll conditionally display the large image and linear gradient only if the widget is being rendered in full color.
Then I’ll bring my image back above the text by adding in a VStack and conditionally displaying my Image if the widget’s in accented mode. I think my layout is looking pretty good. Now I just need to improve the look of my image. I’ll add the widgetAccentedRenderingMode modifier to my Image, setting it to desaturated. widgetAccentedRenderingMode is a SwiftUI modifier that can be applied to Images in widgets. The argument I pass in gives me precise control over how my Image should be shown when in accented mode.
There are five different options widgetAccentedRenderingMode accepts. I’ll show you how each of these modes affect the presentation of images, comparing iOS and macOS accented mode to an accented mode on a watch face. For widgetAccentedRenderingMode, passing in nil is the same as not applying the modifier at all. It applies the primary content color to the image.
For both my iOS and watchOS widget, this makes my image completely white. Passing in accented tints the image to the accent color. On iOS and macOS, both primary and accent colors are white, making my image white. On watchOS, the accent color matches the color on the watch face, making my watchOS widget's image blue.
Passing in desaturated will desaturate the color in the image. This effect looks the same on both iOS and watchOS presentation styles. Passing in accentedDesaturated will apply both the effects, desaturate the colors in the image, and apply the accent color from the selected theme. On iOS, this means the Image is a little whiter, while on watchOS, my desaturated image now takes on the theme’s blue accent color.
Passing in fullColor shows the Image completely unmodified in accented rendering mode. To best blend in with the watch face, this option is ignored on watchOS. For most widgets, use either desaturated or accented desaturated to help the Image content blend in with the rest of the home screen. Use fullColor for Images that represent media content, such as album artwork or a book cover.
Next, I’m excited to show you how to take your widgets into a whole new dimension. With visionOS 26, your visionOS apps can now include widgets. And if you already have a compatible iPhone or iPad app with widgets, those automatically become available in visionOS. All of the system family sizes from iOS and macOS are supported. And widgets in visionOS have interaction and animation capability, just like other platforms. I’ll show you how widgets work on visionOS and new options that WidgetKit provides.
In visionOS, widgets can be added into the room and then pinned to a surface. By default, they’ll take on an elevated look, sitting directly on top of the surface. Widgets can also be recessed, making the widget appear like it’s embedded directly into the surface. If one of these styles isn’t a good fit for your widget, use the supportedMountingStyles modifier on your widget configuration to specify which options should be provided. This modifier is available for both visionOS and iOS widgets.
By default, all widgets will be rendered beneath a glass texture. For visionOS apps, an alternative paper texture can be specified, giving the widget a poster style look. Here on the left I have my frequent beverage widget configured with glass, and on the right I’m testing out the same widget but with the paper texture applied.
Use the widgetTexture modifier on your widget configuration to specify whether your widget should be rendered with a paper texture or behind a pane of glass. To complete the look of poster style widgets, a new systemExtraLargePortrait widget family is available in visionOS. This is a vertically oriented version of the existing horizontal systemExtraLarge widget family.
Add this to your widget configuration using the supportedFamilies modifier. In visionOS, the widget’s color theme can also be customized. By default, the widget will appear in a full color presentation. If I select this green theme, the widget’s content is displayed using accented rendering mode. The widget frame and content get tinted with my selected color. Then the background is removed and replaced with a solid color that complements the selected color theme.
This style applies the same rendering approach discussed earlier for iOS and macOS. Use the same techniques I covered to make your widget look its best in these color themes. Use the widgetAccentedRenderingMode modifier to customize the presentation of your images. And use the widgetRenderingMode environment variable to conditionally apply more substantial modifications.
On visionOS, widgets can be brought into your environment and placed on multiple surfaces. As you move about your space, widgets stay in their fixed position. Even if a widget is far away on a wall across the room, it remains visible. Just like real physical objects, widgets that are further away are smaller and harder to see. Unlike physical objects, widgets in visionOS can adapt based on their distance using a new level of detail API. I’ll show you how I can add levelOfDetail to my caffeine tracking widget.
Here I have my existing widget that shows my total caffeine for the day, the last beverage I drank, as well as a handy button to add another drink to the log. When this widget is further away from me, I’d like to make the total caffeine value larger and easier to read. I’d also like to hide the button, since it will be harder to hit further away. Here’s my caffeine tracker widget. I want to update my TotalCaffeineView, changing its size, and conditionally show or hide my LogDrinkView at the bottom.
First, I’ll add this environment property, levelOfDetail, to my view. LevelOfDetail can be one of two values. Default is the normal level of detail expected of widgets. On visionOS, widgets are shown in the default level of detail when they are at a comfortable distance. If the widget is physically far enough away, the level of detail changes to simplified, providing an opportunity to display a simpler, more easily glanceable representation of the widget.
I’ll add this conditional around my LogDrinkView to only display if the levelOfDetail is default. Now I need to update the caffeine amount. In my total caffeine view, I’m displaying the title and the formatted total caffeine amount. First, I’ll add the levelOfDetail environment variable to my view. In order to make this bigger, I’ll conditionally change the font of my caffeine amount from title to large title based on the level of detail. Now, whenever the widget is far enough away, my widget transitions to show a simpler, more easily glanceable version of my widget. And just like timeline changes within your widget, level of detail changes also animate.
To learn more about when to customize your spatial style, recommendations for level of detail, and other considerations for bringing your widgets to this new platform, check out “Design Widgets for visionOS.” Shifting gears, now widgets and Live Activities, are hitting the open road with CarPlay. In CarPlay Ultra, widgets appear in one or more stacks to the left of the dashboard. And starting with iOS 26, this feature now comes to all CarPlay cars. Widgets can be configured in the Settings app under CarPlay. In CarPlay, glanceable information, large typography, and legibility are all important to help make your widget easy to read on the car’s display.
To support this, CarPlay renders widgets in a StandBy style, using the systemSmall family in fullColor with the widget background removed. Widget interactions are supported on touchscreens, and you can use the CarPlay simulator available on the developer site to test your widget. Get more tips on how to adapt your widget for a StandBy presentation in “Bring widgets to new places” from WWDC 23.
Live Activities can also be displayed on the home screen in CarPlay. By default, your Live Activity’s Dynamic Island leading and trailing views will be shown. Here I have my Coffee Order Tracking Live Activity being displayed in CarPlay. This is a good start, but with just a little bit of extra code, I can make this even better.
Here’s the code for my Live Activity. Currently, my leading and trailing views are shown. To customize this Live Activity presentation for CarPlay, I’ll add the supplementalActivityFamilies modifier, passing in small as an argument to my ActivityConfiguration. Now, rather than using the leading and trailing views, CarPlay will display my ActivityView, the same view that’s used on the iPhone lock screen.
For some Live Activities, this may look great, but mine is a little cramped with some of my content cut off. Luckily, I can customize this even further. Here is my ActivityView. I’ll add the activityFamily environment variable to my view. With that added, in my views body, I can conditionally display different content or adjust the layout to provide a great experience.
When the activityFamily is small, I’ll provide a shop order view that is optimized for a smaller layout. Otherwise, I’ll display my default order view. With that little bit of extra code, my Live Activity now looks great in CarPlay. Now I can quickly glance and see how much longer until my order is ready.
By adopting supplementalActivityFamily, I’ve also greatly improved how my Live Activity looks when presented on a paired Apple Watch. Your iPhone app gets this automatically, without the need for a separate watchOS app. To learn more about how to make your Live Activity look its best in the Smart Stack on watchOS, check out “Design Live Activities for Apple Watch.” And check out “Turbocharge your app for CarPlay” to learn more about how to really accelerate your widgets in CarPlay. CarPlay isn’t the only new place for Live Activities.
Live Activities from a paired iPhone will now appear in macOS Tahoe. Just like in the Dynamic Island on iPhone, my coffee order tracker Live Activity presents the leading and trailing views together in the menu bar. When the Live Activity is selected, the lock screen presentation from iPhone will appear. Clicking on the lock screen presentation will launch the associated app using iPhone Mirroring.
Live Activities on macOS can be provided by iPhones running iOS 18 and later. There are no code changes required, and just like iPhone widgets on macOS, they support interaction and deep links. Now I’ll cover new places for controls on macOS and watchOS. On macOS, controls can be provided by apps running on the Mac, whether they’re built with the macOS SDK, Catalyst, or from iOS apps running on Apple Silicon Macs. Controls can be added in the Control Center. The same small, medium, and large presentations available on iOS can also be configured on macOS.
Controls can also be placed directly on the menu bar. Now that I’ve added a coffee tracker control to my app on macOS, I can easily update my coffee log right from the menu bar. In watchOS 26, controls can appear in three places. They can be configured in the Control Center, accessible from the side button. Controls can also be executed when pressing the Action button on Apple Watch Ultra.
And they can be configured in the Smart Stack, where they appear alongside other widgets displaying the control’s symbol, title, and current value. Controls can be provided from a watchOS app or from an iPhone app on a paired device. For an extensive guide to building controls, check out “Extend your app’s controls across the system” from WWDC24. Next, I’ll show you relevance widgets in the Smart Stack in watchOS 26.
Relevance widgets
I’ve added a widget to my caffeine tracking app on watchOS that helps me keep track of when my favorite coffee shops have half price happy hours. There’s two things I’d like to improve with my widget. First, since I’m tracking the happy hours for several coffee shops, often their happy hours overlap, meaning the content on my widget in the Smart Stack is pretty cramped.
Second, the happy hours tend to happen around the same time. So for the rest of the day, my widget isn’t very useful. I’d really like my happy hours widget to only show up in the Smart Stack if it was relevant and show more detailed information for each active happy hour. With relevance widgets in watchOS 26, I can do just that. I’ll show how I can configure my happy hours as a relevance widget.
To define a relevance widget, create a Widget type and instead of a StaticConfiguration or AppIntentConfiguration, provide a RelevanceConfiguration. Just like other configurations, it takes a kind String, a provider object, and a closure to transform your custom entry into a SwiftUI view. The provider type is a RelevanceEntriesProvider. The placeholder and relevance methods are similar to TimelineEntriesProvider. For placeholder, I can return a simple Entry to be displayed while my content is getting prepared. For relevance, I first fetch a collection of configuration objects that I’ve defined.
For my happy hours widget, a configuration is relevant between the start and end time of that happy hour. I’ll define the relevance attribute with this context using the date interval for each happy hour. Then I implement the entry method. Unlike a Timeline widget, a RelevanceEntriesProvider only provides a single entry for a configuration. I have all the data I need for this entry in my configuration, the shop data and the time range for the happy hour, so I can create that immediately. If I needed any other data or assets, I could fetch them here since this method is marked async.
Now with my relevance widget set up, my happy hours widget only displays in the Smart Stack when it’s relevant. Plus, if I have multiple configurations that are relevant at the same time, I can see multiple instances of my widget in the Smart Stack. Relevance widgets are a powerful new feature in watchOS 26, giving you the ability to directly connect widget content with its relevance. These widgets are a great addition on their own or alongside existing timeline based widgets.
To learn more about Relevance widgets, check out “What’s new in watchOS 26” with Anne. Now that there’s so many more places and platforms that widgets can appear, I’d like to keep my widgets always up to date wherever they are.
Push widget updates
I’ll cover what options are available for refreshing my widget, starting with scheduled widget reloads. In this diagram, on the left I have my app bundle, which contains my app as well as my widget extension. On the right side, I have a box representing WidgetKit. When a widget is configured on a device, such as on the iPhone home screen, or on a watch face, WidgetKit requests a timeline from the widget extension. The extension responds with a widget timeline, which includes a TimelineReloadPolicy. WidgetKit uses this to determine the next appropriate time to reload that widget.
Using the TimelineReloadPolicy is a great option for widgets that need to update at regular intervals, such as a widget that displays the hours of a cafe, a weather widget, or a stock widget. The scheduled timeline reload is budgeted by the system to help maintain performance and battery life.
The WidgetCenter API is another option available to apps. Inside an app, if a data change occurs that should be reflected in its widget, WidgetCenter’s reloadAllTimelines or reloadTimelines(ofKind:) method can be called. This tells WidgetKit that the widget’s content is outdated and needs to be reloaded. WidgetKit then requests a timeline from the widget extension to update the widget.
This is a great option if widget content primarily changes inside the app, such as updating a caffeine log, changing a note, or checking off a reminder. Since the app is running when this API is called, the system does not budget this request. But what happens if a data change occurs on the server or on another device? This is where widget push updates come in. A server tracking data changes can send a push notification to APNs, which will tell WidgetKit to reload that app’s widgets. And like other updates, WidgetKit will then request an updated timeline from the widget.
Widget push updates are a great tool if data can change external to that device. Just like the TimelineReloadPolicy, widget push notification updates are also budgeted to maintain performance and battery life. With this capability, widgets now have a full suite of reload options to handle a variety of situations. These are not mutually exclusive. Some widgets may want to use two or even all three of these options.
With widget push updates, my widget’s caffeine log can stay up to date across devices whether I log an update on my iPad app, my Vision Pro widget, or my macOS menu bar control. I’ll show you how I can add this to my widget. I’ll create a WidgetPushHandler and add it to my widget configuration, add the Push Notification entitlement to my widget extension, and construct a widget update push request.
First, I’ll create a struct conforming to the WidgetPushHandler protocol. This type is how we notify you when your push token changes or when the set of configured widgets changes. Use the pushTokenDidChange method as an opportunity to send your push token and widget info to your server. Next, I need to update my widget configuration. Here I have the configuration for my caffeine tracker widget. I’ll add the pushHandler modifier to my widget to register its support for push notifications. For this modifier, I pass in the type of the widget pushHandler I implemented.
Finally, in Xcode, I’ll go to the Signing and Capabilities tab for my widget extension. Here, I’ll add the push notification entitlement so it can communicate with APNs. Now that my widget is configured for push updates, I’ll show you how to send a widget update push notification. To update your widget via push notification, send an HTTPS POST request to the Apple Push server. Use the widget push token provided in your WidgetPushHandler as the last part of the request path. For your headers, use the widgets APNs push type, and set the APNS topic header using the app’s bundle ID, suffixed with .push-type.widgets.
In the body of your request in the aps dictionary, set the value of the content changed key to true. To learn more about push notifications, watch the “Push Notifications Primer”. And check out “Meet Push Notifications Console” to see how you can easily test push notification requests. Push updates for widgets help keep widget content more up to date, but are performed opportunistically and aren’t a direct replacement for other notification experiences. If you have an urgent or important update to show, provide a User Notification. If you have updates occurring regularly during a limited period of time, such as a beverage order, updating sports scores, or flight updates, use a Live Activity.
Use widget push updates to keep widget content up to date. Widget push updates are available across all platforms that support widgets. When you send a widget push notification, this will update all of the push enabled widgets configured on the device. Remember that these widget reloads are budgeted. Try to keep your update pushes limited, such as by throttling updates on your server. And during development and test, you can use the WidgetKit developer mode in Settings to ignore push and reload budgets for your app.
I've covered a lot today. Take some time to explore these new platforms for widgets. Check out some of the videos listed earlier to help get inspired. Make sure your widgets look great in new appearances on iOS and macOS. And finally, if your widgets data may be updated from external sources or other devices, consider adding push notifications to keep them up to date. I’m so excited about all the new capabilities and places for widgets. I can’t wait to take your widgets with me wherever I go. Thanks for watching.