文章

iOS小组件 - iOS 17 交互性小组件

iOS 17 上苹果为小组件新增了两个交互性组件 Botton / Toggle. 通过这两个 View, 我们可以直接在小组件中执行某些 App 的功能.

其内部基于 AppIntent 框架实现, 核心逻辑是:

  1. App 基于 AppIntent 给系统暴露一个可执行的功能
  2. 小组件中交互组件点击, 系统执行 App 暴露的功能

具体实现步骤如下:

创建组件

创建一个小组件是基本操作, 步骤如下, 细节不再赘述

File -> New Target -> widget extension

封装一个 Intent 结构, 暴露 App 方法给系统

自定义一个结构体, 遵守 AppIntent 协议.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import Foundation
import AppIntents

@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
struct ClickCountIntent: AppIntent {
    
    static let intentClassName = "ClickCountIntent"
    
    static var title: LocalizedStringResource = "Click Count Intent"
    static var description = IntentDescription("record click count")
    
    @Parameter(title: "Selected Hero")
    var count: String?
    
    init(count: String? = nil) {
        self.count = count
    }
    init() {
        count = ""
    }
    
    func perform() async throws -> some IntentResult {
        
        print(self.count ?? "这里可以执行某个 App 的方法")
        /*
         AppIntent 返回之前要存档好所有必要数据, 因为一返回就会直接刷新 timeline
         */
        
        let count = UserDefaults.standard.integer(forKey: "count")
        UserDefaults.standard.setValue(count + 1, forKey: "count")
        
        return .result()
    }
}

小组件部分支持 iOS 17 新的交互组件

iOS 17 新交互组件有 Button / Toggle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct widgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        VStack {
            // 以 Button 为例, 记录点击次数
            let count = UserDefaults.standard.integer(forKey: "count")
            Button(intent: ClickCountIntent(count: "已经点击次数 \(count)")) {
                if count > 0 {
                    Text("Click Me (\(count))")
                        .foregroundStyle(.yellow)
                }else{
                    Text("Click Me")
                        .foregroundStyle(.yellow)
                }
                
            }.tint(.clear)
        }
    }
}

一些细节

Intent 的 perform() 函数执行完系统会立即刷新时间线, 此刻处理的数据要存档, 避免小组件读取的时候信息丢失

因为 perform() 函数执行完到刷新时间线重新绘制 UI 有一小段延迟, 为了优化体验, 苹果提供了 .invalidatableContent() 修饰符来修饰某个视图,这样在点击按钮之后到 UI 更新之前,更新内容都是无效的。

本文由作者按照 CC BY 4.0 进行授权