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?
Adding Your Second Widget
Congratulations, you completed your first interactive widget! It’s time to add your second one. :]
Before you dive into this, review some concepts about widget sizes and best practices.
Widgets Design Concepts
WidgetKit supports multiple devices and different sizes for each device.
On iOS, the supported sizes are Small, Medium and Large. Each size provides a different layout and amount of space for the detail, so you should consider what and how to present the data in your widget.
I like to follow this general rule of thumb while also looking at widgets designed by Apple.
- Small widgets focus on a specific object, such as a task.
- Bigger sizes offer a broader view, either to show more object details or to present multiple objects.
Adding a TodoList Widget
With this second widget, you use the extra space of the medium and large widget sizes to provide the user with a broader view, presenting multiple tasks at once.
To streamline the UI, the list includes TODO tasks only, and you use a toggle button to mark the task as done.
Start by adding a new file named TodoListWidget.swift to TraskWidgets target.
Now, add the basic struct describing the widget in the new file. Open TodoListWidget.swift and add the following content.
import WidgetKit
import SwiftUI
struct TodoListWidget: Widget {
let kind: String = "TodoListWidget"
var body: some WidgetConfiguration {
// 1. Static configuration
StaticConfiguration(
kind: kind,
// 2. Timeline provider
provider: TodoListProvider()
) { entry in
// 3. SwiftUI View
TodoListWidgetEntryView(entry: entry)
}
.configurationDisplayName("Todo List")
.description("Shows the list of todo tasks")
// 4. Supported families
.supportedFamilies([.systemMedium, .systemLarge])
}
}
Here is a summary of the key points in the widget definition.
- You used a
StaticConfiguration
because the widget will provide a list of tasks to display itself. In the previous widget, you used anAppIntentConfiguration
since the user had the choice of selecting a task via the App Intent. - As you did for the status widget, you’ll write a timeline provider that returns the list of TODO tasks to present along with the update policy.
- This is the main view representing the widget, you’ll add it soon.
- You indicate that the widget supports just the medium and large sizes.
TodoList Timeline Provider
As seen for the other widget, the Timeline provider, TodoListProvider
, provides the items to present in the widget.
Add the following code to TodoListWidget.swift.
struct TodoListProvider: TimelineProvider {
// 1. Filter Todo task
private var storedTodos: [TaskItem] {
UserDefaultStore()
.loadTasks()
.filter(\.isToDo)
}
func placeholder(in context: Context) -> TodoListEntry {
return TodoListEntry(date: Date(), todos: TaskItem.sampleTasks)
}
func getSnapshot(in context: Context, completion: @escaping (TodoListEntry) -> Void) {
completion(TodoListEntry(date: Date(), todos: storedTodos))
}
func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
let entry = TodoListEntry(date: Date(), todos: storedTodos)
completion(Timeline(entries: [entry], policy: .never))
}
}
// 2. Timeline entry
struct TodoListEntry: TimelineEntry {
let date: Date
let todos: [TaskItem]
}
Two things are worth emphasizing.
-
TodoListProvider
filters the tasks to provide just the TODO items. -
TodoListEntry
provides a list of TODO tasks that the widget will show.
TodoList Widget View
Finally, add the SwiftUI view for the widget to TodoListWidget.swift.
struct TodoListWidgetEntryView: View {
var entry: TodoListProvider.Entry
// 1. Widget family
@Environment(\.widgetFamily) var widgetFamily
private var listLength: Int { widgetFamily == .systemLarge ? 5 : 3 }
var body: some View {
// 2. List not allowed in Widgets
ForEach(entry.todos.prefix(listLength)) { todo in
HStack {
Label(todo.name, systemImage: todo.category.systemImage)
.padding([.vertical])
.font(.headline)
.strikethrough(todo.isCompleted)
.foregroundColor(todo.tint.color)
.frame(maxWidth: .infinity, alignment: .leading)
// 3. Toogle using AppIntent
Toggle(isOn: todo.isCompleted, intent: TaskIntent(taskEntity: TaskEntity(task: todo))) {
Image(systemName: "checkmark")
}
}
}
.padding()
.transition(.push(from: .bottom))
.containerBackground(.clear, for: .widget)
}
}
- The
widgetFamily
environment variable reflects the actual size of the widget. In this case, you use this size to present a different number of tasks. - Not all SwiftUI constructs are available in WidgetKit.
-
Toggle(isOn:,intent:,_:)
is new to iOS 17. It allows the app to call an App Intent when the state changes.
Multiple Widgets with WidgetBundle
Add the new widget to the list of available widgets in the app to support multiple widgets in your extension.
Open TaskWidgetBundle.swift and add TodoListWidget()
widget to the widget bundle.
@main
struct TaskWidgetBundle: WidgetBundle {
var body: some Widget {
TaskStatusWidget()
TodoListWidget()
}
}
Build and run, add the new widget, and enjoy your shiny new interface for task management.
Where to Go From Here?
You can download the complete project using the Download Materials button at the top or bottom of this tutorial.
During this tutorial, you achieved the following results.
- Improved the app usability by adding interaction through widgets.
- Designed some great SwiftUI widget views and customized their animations based on the content.
- Learned how to add multiple widgets to an extension and how to exploit different sizes to show different pieces of data.
Furthermore, you laid down the infrastructure to use your app with Shortcuts and Siri using your App intents.
The WidgetKit page on the Apple Developer website offers great reference documentation for any details you want to master.
I also encourage you to go over the SwiftUI Charts for WidgetKit course to add some great charts to your next widgets.
As always, join us in the discussion below for any suggestions, feedback, or questions you might have.