2023 EssentialsSwiftUI & UI FrameworksApp Services
WWDC23 · 19 min · Essentials / SwiftUI & UI Frameworks / App Services
Bring widgets to life
Learn how to make animated and interactive widgets for your apps and games. We’ll show you how to tweak animations for entry transitions and add interactivity using SwiftUI Button and Toggle so that you can create powerful moments right from the Home Screen and Lock Screen.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 9 snippets
Usage for the container background modifier
.containerBackground(for: .widget) {
Color.cosmicLatte
} Define a preview for the caffeine tracker widget
#Preview(as: WidgetFamily.systemSmall) {
CaffeineTrackerWidget()
} timeline: {
CaffeineLogEntry.log1
CaffeineLogEntry.log2
CaffeineLogEntry.log3
CaffeineLogEntry.log4
} Add a numeric text content transition
struct TotalCaffeineView: View {
let totalCaffeine: Measurement<UnitMass>
var body: some View {
VStack(alignment: .leading) {
Text("Total Caffeine")
.font(.caption)
Text(totalCaffeine.formatted())
.font(.title)
.minimumScaleFactor(0.8)
.contentTransition(.numericText(value: totalCaffeine.value))
}
.foregroundColor(.espresso)
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
}
} Set up transition on LastDrinkView
struct LastDrinkView: View {
let log: CaffeineLog
var body: some View {
VStack(alignment: .leading) {
Text(log.drink.name)
.bold()
Text("\(log.date, format: Self.dateFormatStyle) · \(caffeineAmount)")
}
.font(.caption)
.id(log)
.transition(.push(from: .bottom))
}
var caffeineAmount: String {
log.drink.caffeine.formatted()
}
static var dateFormatStyle = Date.FormatStyle(
date: .omitted, time: .shortened)
} Configuring animation for the transition
struct LastDrinkView: View {
let log: CaffeineLog
var body: some View {
VStack(alignment: .leading) {
Text(log.drink.name)
.bold()
Text("\(log.date, format: Self.dateFormatStyle) · \(caffeineAmount)")
}
.font(.caption)
.id(log)
.transition(.push(from: .bottom))
.animation(.smooth(duration: 1.8), value: log)
}
var caffeineAmount: String {
log.drink.caffeine.formatted()
}
static var dateFormatStyle = Date.FormatStyle(
date: .omitted, time: .shortened)
} Reload the timeline for a widget
WidgetCenter.shared.reloadTimelines(ofKind: "LocationForecast") App intent to log a caffeine drink
import AppIntents
struct LogDrinkIntent: AppIntent {
static var title: LocalizedStringResource = "Log a drink"
static var description = IntentDescription("Log a drink and its caffeine amount.")
(title: "Drink", optionsProvider: DrinksOptionsProvider())
var drink: Drink
init() {}
init(drink: Drink) {
self.drink = drink
}
func perform() async throws -> some IntentResult {
await DrinksLogStore.shared.log(drink: drink)
return .result()
}
} Create view to log a new drink
struct LogDrinkView: View {
var body: some View {
Button(intent: LogDrinkIntent(drink: .espresso)) {
Label("Espresso", systemImage: "plus")
.font(.caption)
}
.tint(.espresso)
}
} Use the invalidatable content modifier
struct TotalCaffeineView: View {
let totalCaffeine: Measurement<UnitMass>
var body: some View {
VStack(alignment: .leading) {
Text("Total Caffeine")
.font(.caption)
Text(totalCaffeine.formatted())
.font(.title)
.minimumScaleFactor(0.8)
.contentTransition(.numericText(value: totalCaffeine.value))
.invalidatableContent()
}
.foregroundColor(.espresso)
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
}
} Related sessions
-
7 min -
30 min -
17 min -
18 min -
27 min -
31 min