介绍
- 增加
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")
}