Разработка
Создание интерактивного виджета с помощью App Intents
В этом примере мы создадим интерактивный виджет, который увеличивает глобальный счетчик в целевом приложении.
В этом примере мы создадим интерактивный виджет, который увеличивает глобальный счетчик в целевом приложении, используя нажатия кнопки и App Intents.
Этот пример требует двух разных файлов из-за расширения Widget.
Начнем с основного целевого приложения.
Здесь мы сначала создаем общую структуру, которую будут использовать как целевое приложение, так и расширение виджета. Она просто хранит счетчик в пользовательских настройках по умолчанию с включенными App Groups (это позволяет и приложению, и его расширению виджета получить к нему доступ).
Далее, во второй части, используем ExampleIntent, — это то, что приводит в действие нажатие кнопки в виджете. Тут просто получаем доступ к общему счетчику, увеличиваем его и возвращаемся. Если вам нужно знать, как настроить базовый App Intent, посмотрите этот фрагмент.
xxxxxxxxxx
import SwiftUI
import AppIntents
import WidgetKit
// 1
class Counter {
private static let sharedDefaults: UserDefaults = UserDefaults(suiteName: "group.examples.sjc")!
static func incrementCount() {
var count = sharedDefaults.integer(forKey: "count")
count += 1
sharedDefaults.set(count, forKey: "count")
}
static func currentCount() -> Int {
sharedDefaults.integer(forKey: "count")
}
}
// 2
struct ExampleIntent: AppIntent {
static var title: LocalizedStringResource = "Increment Count"
static var description = IntentDescription("Increments a shared count with the main app.")
func perform() async throws -> some IntentResult {
Counter.incrementCount()
return .result()
}
}
struct ContentView: View {
@Environment(\.scenePhase) private var phase
@State private var count: Int = 0
var body: some View {
VStack {
Text("Count: \(count)")
}
.padding()
.onChange(of: phase) {
count = Counter.currentCount()
}
}
}
А в расширении Widget используем такой код:
xxxxxxxxxx
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), count: "\(Counter.currentCount())")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
completion(SimpleEntry(date: Date(), count: "\(Counter.currentCount())"))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let timeline = Timeline(entries: [SimpleEntry(date: Date(), count: "\(Counter.currentCount())")], policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let count: String
}
struct WidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Count:")
Text(entry.count)
// 3
Button(intent: ExampleIntent()) {
Text("Increment Count")
}
}
.containerBackground(.fill.tertiary, for: .widget)
}
}
struct Widgets: Widget {
let kind: String = "Widgets"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetsEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
Тут мы используем инициализатор Button, который принимает App Intent. Мы передаем в него наш ExampleIntent для выполнения. Когда он будет выполнен, WidgetKit запросит свежую временную шкалу и, таким образом, покажет наш обновленный счетчик в виджете (и в самом приложении).
В результате получаем общий счетчик для виджета и целевого приложения:
Вот и все, до следующего раза ✌️
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.14
-
Видео и подкасты для разработчиков4 недели назад
Исследуем мир фото и видео редакторов
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.13
-
Разработка2 недели назад
Конец продуктовой разработки в том виде, в котором мы ее знаем