Interactive Widgets With SwiftUI
Discover how iOS 17 takes widgets to the next level by adding interactivity. Use SwiftUI to add interactive widgets to an app called Trask. Explore different types of interactive widgets and best practices for design and development. By Alessandro Di Nepi.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Interactive Widgets With SwiftUI
20 mins
- Getting Started
- Recapping WidgetKit
- Adding an iOS Widget
- Widget Code Structure
- Data Sharing With The App
- Timeline Provider
- Updating Widgets
- Making the Widget Interactive
- Types of Interactivity
- Widgets and Intents
- Adding the Intent
- Binding Everything Together
- Animating the Changes
- Fixing the Animation For a Digit
- Adding More Animations
- Keeping the App in Sync
- Adding Your Second Widget
- Widgets Design Concepts
- Adding a TodoList Widget
- TodoList Timeline Provider
- TodoList Widget View
- Multiple Widgets with WidgetBundle
- Where to Go From Here?
Apple introduced Widgets in iOS 14 and brought a fresh look that changed our phone’s home screens. The framework evolved through the years, adding a powerful means of keeping users updated with their data.
iOS 17 takes widgets to the next level by introducing interactivity. Users can now interact with your app in a new, innovative way that wasn’t possible before.
By making your app’s essential actions available in a widget, your users have a more convenient and engaging way to interact with your app.
In this tutorial, you’ll add interactive widgets to the Trask app using SwiftUI.
If you’re thinking about learning SwiftUI, widgets’ simple views are a great place to start with.
This tutorial covers the following topics.
- What interactive widgets are and how they work.
- How to create interactive widgets with a SwiftUI animation.
- Different types of interactive widgets that you can create.
- Best practices for designing and developing interactive widgets.
Although there are no strict prerequisites, a basic knowledge of SwiftUI and WidgetKit might be helpful. Anyway, don’t worry, you’ll have a quick recap to start off on the right foot.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial. Open the starter project (Trask.xcodeproj) in the Starter folder.
Build and run the project, and you should see the Trask initial screen.
Trask is a general tracker app that tracks different tasks/things/habits during the day, such as the number of glasses of water, medicine, yoga, and so on.
The first time you launch the app, Trask creates some sample data so you can see the different types of tasks you can create.
- Task with multiple steps.
- “TODO” task with just one step.
When tapping the plus button, the app advances the task, and once it reaches its target, it passes in the done state.
The user can delete tasks by swiping left on them and can add new ones using the button at the bottom of the View.
Recapping WidgetKit
Before you get into the hot topic of this tutorial, familiarize yourself with some basic concepts on WidgetKit to build common terminology for the rest of the tutorial.
Adding an iOS Widget
Trask comes with a static widget to follow the status of a selectable task.
Add an instance of the widget to see how it looks.
- Build and run the project.
- Minimize the app.
- Long press on an empty area of the screen.
- Then tap the + button, search for Trask, and select the widget available.
You’re now ready to jump into the code structure to see how it works.
Widget Code Structure
The TraskWidgets folder of the starter project contains all the files related to the widget.
-
TaskIntent
is an intent conforming to theWidgetConfigurationIntent
protocol. Here, the intent allows the task selection in the Edit Widget menu. -
TaskStatusWidget
is the actual widget. Four parts compose the widget file. -
TaskTimelineProvider
specifies when iOS should refresh the widget screen. -
TaskEntry
represents the model of the widget view. It contains a date iOS uses to update the widget view with the task item. -
TaskStatusWidgetEntryView
defines the widget view using SwiftUI. It contains a timeline entry as a parameter, and it should lay out the widget based on this parameter value. -
TaskStatusWidget
binds all the parts together within aWidgetConfiguration
. - Finally,
TraskWidgetBundle
declares all the extension’s widgets.
Data Sharing With The App
As you may see, the widget code is contained in a separate Xcode target, and iOS runs the widget in a process different from the app. This detail might seem subtle, but it’s crucial when considering that the app and the widget need to share the same data. The widget code can’t simply call some functions in the app target. Among the different possibilities, Trask uses a UserDefault
store on an App Group container shared between the app and the widget.
Timeline Provider
Timeline is a key concept of Widgets. To preserve battery and system resources, iOS doesn’t constantly run your widget. Instead, it asks your timeline provider to generate a series of timeline entries to render your widget and present it at the right time.
Your TaskTimelineProvider
defines three methods.
-
placeholder(in:)
should return some sample data to render the placeholder UI while waiting for the widget to be ready. SwiftUI applies a redaction effect to this view. -
snapshot(for:in:)
provides the data to render the widget in the gallery presented when choosing a widget. -
timeline(for:in:)
is the main method that returns the timeline entries to present at the specified time.
Updating Widgets
As said above, the timeline(for:in:)
returns the array of entries at the specified time, but what happens after the last widget view is presented? Enter the widget update strategy!
When returning the timeline of entries, you also provide one strategy for updating the timeline. You may choose between the three options below.
-
.atEnd
recomputes the timeline after the last date in the timeline passes. -
.after(_:)
specifies approximately when to request a new timeline. -
.never
tells the system to never recompute the timeline. The app will prompt WidgetKit when a new timeline is available.
In our case, the Trask timeline provider returns the .never
policies since there is no need for the widget to update its view. The only way to update the status of a task is through the app when the user taps to step a task…until the next chapter. :]
Making the Widget Interactive
Wow…that was a long warmup, but now you’re ready to add interaction to the Trask status widget.
Types of Interactivity
Starting with iOS 17, iPadOS 17 and macOS 14, Apple allows two main ways of interactivity with your widget: buttons and toggles.
- Buttons are suitable to represent an action on the widget content.
- Toggles better identify a binary actionable state on/off. Such as our TODO task status.
As the first improvement, you’ll add a step button to the Trask Status Widget so users can progress their favorite tasks without opening the app.