В этом примере мы создадим интерактивный виджет, который увеличивает глобальный счетчик в целевом приложении, используя нажатия кнопки и App Intents.
Этот пример требует двух разных файлов из-за расширения Widget.
Начнем с основного целевого приложения.
Здесь мы сначала создаем общую структуру, которую будут использовать как целевое приложение, так и расширение виджета. Она просто хранит счетчик в пользовательских настройках по умолчанию с включенными App Groups (это позволяет и приложению, и его расширению виджета получить к нему доступ).
Далее, во второй части, используем ExampleIntent, — это то, что приводит в действие нажатие кнопки в виджете. Тут просто получаем доступ к общему счетчику, увеличиваем его и возвращаемся. Если вам нужно знать, как настроить базовый App Intent, посмотрите этот фрагмент.
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 используем такой код:
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 запросит свежую временную шкалу и, таким образом, покажет наш обновленный счетчик в виджете (и в самом приложении).
В результате получаем общий счетчик для виджета и целевого приложения:
Вот и все, до следующего раза ✌️