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.

1 (1) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

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.

  1. You used a StaticConfiguration because the widget will provide a list of tasks to display itself. In the previous widget, you used an AppIntentConfiguration since the user had the choice of selecting a task via the App Intent.
  2. 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.
  3. This is the main view representing the widget, you’ll add it soon.
  4. 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.

  1. TodoListProvider filters the tasks to provide just the TODO items.
  2. 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)
  }
}
  1. 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.
  2. Not all SwiftUI constructs are available in WidgetKit.
  3. 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.

Trask's TODO List Widget.

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.