介绍

  • 增加containerBackground修饰符可以设置 Widget 的布景。
  • 增加widgetContentMargins环境变量可以设置 Widget 的边距。
  • 重要更新:可以通过AppIntent在不打开 App 的情况下进行交互操作,但交互的 View 现在仅支撑 Button 与 Toggle。
extension Button {
    public init<I: AppIntent>(
        intent: I,
        @ViewBuilder label: () -> Label
    )
}
extension Toggle {
    public init<I: AppIntent>(
        isOn: Bool,
        intent: I,
        @ViewBuilder label: () -> Label
    )
}

案例

效果

完成

import AppIntents
import Foundation
import SwiftUI
import WidgetKit
// MARK: - Model
class Counter {
    @AppStorage("count", store: UserDefaults(suiteName: "Widget2023")) static var count = 0
    static func incrementCount() {
        count += 1
    }
    static func decrementCount() {
        count -= 1
    }
    static func currentCount() -> Int {
        return count
    }
}
// MARK: - AppIntent
struct CountIntent: AppIntent {
    static var title: LocalizedStringResource = "CountIntent"
    static var description: IntentDescription = IntentDescription("CountIntent")
    // AppIntent的输入参数
    @Parameter(title: "isIncrement")
    var isIncrement: Bool
    init() {
    }
    init(isIncrement: Bool) {
        self.isIncrement = isIncrement
    }
    func perform() async throws -> some IntentResult {
        if isIncrement {
            Counter.incrementCount()
        } else {
            Counter.decrementCount()
        }
        return .result()
    }
}
// 宿主App
struct ContentView: View {
    @Environment(\.scenePhase) private var phase
    @State private var count = 0
    var body: some View {
        VStack {
            Text("Count: \(count)")
                .font(.largeTitle)
                .foregroundStyle(.primary)
            HStack {
                Button {
                    count += 1
                    Counter.incrementCount()
                    WidgetCenter.shared.reloadAllTimelines()
                } label: {
                    Image(systemName: "plus")
                        .font(.largeTitle)
                }
                Button {
                    count -= 1
                    Counter.decrementCount()
                    WidgetCenter.shared.reloadAllTimelines()
                } label: {
                    Image(systemName: "minus")
                        .font(.largeTitle)
                }
            }
        }
        .padding()
        .onChange(of: phase) {
            count = Counter.currentCount()
        }
    }
}
// MARK: - Widget
struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), count: "\(Counter.currentCount())")
    }
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
        let entry = SimpleEntry(date: Date(), count: "\(Counter.currentCount())")
        completion(entry)
    }
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        let timeline = Timeline(entries: [SimpleEntry(date: Date(), count: "\(Counter.currentCount())")], policy: .atEnd)
        completion(timeline)
    }
}
struct SimpleEntry: TimelineEntry {
    let date: Date
    let count: String
}
struct CountWidgetEntryView: View {
    // iOS17新增环境变量,设置边距
    @Environment(\.widgetContentMargins) var margins
    var entry: Provider.Entry
    var body: some View {
        VStack {
            Text("Count: \(entry.count)")
            HStack {
                // 交互
                Button(intent: CountIntent(isIncrement: true)) {
                    Image(systemName: "plus.circle")
                }
                Button(intent: CountIntent(isIncrement: false)) {
                    Image(systemName: "minus.circle")
                }
            }
            .font(.largeTitle)
        }
        .containerBackground(.fill.tertiary, for: .widget) // iOS17新增,设置小组件布景
        .padding(.top, margins.top) // 设置顶部边距
    }
}
struct CountWidget: Widget {
    let kind: String = "CountWidget"
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            CountWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("CountWidget")
        .description("This is a CountWidget.")
    }
}
// MARK: - 预览
#Preview(as: .systemSmall) {
    CountWidget()
} timeline: {
    SimpleEntry(date: .now, count: "99")
}
#Preview(as: .systemMedium) {
    CountWidget()
} timeline: {
    SimpleEntry(date: .now, count: "99")
}
#Preview(as: .systemLarge) {
    CountWidget()
} timeline: {
    SimpleEntry(date: .now, count: "99")
}