← 所有文章

iOS 26 的小组件界面:一个 App Intent,多处呈现

多年来,小组件不过是一张快照。系统按时间线渲染出您的视图,用户看一眼,任何点按都只会打开应用。这套模式已成过去。自 iOS 17 起,小组件就能承载按钮和开关,无需启动任何东西即可运行代码,而它们运行的正是一个 App Intent1。控制中心控件如此,实时活动内部的按钮也是如此。一旦看清这一点,iOS 26 的小组件界面就不再像三个彼此独立的 API,而是合而为一:它们都是系统呈现您 App Intents 的场所,各自占据不同的展示空间,底层却共用同一套管道。

这种重新审视,正是本文的核心。学会一次 App Intent,就能点亮主屏幕、锁定屏幕、控制中心、操作按钮、灵动岛,以及(还是同一个 intent)Siri 与 Apple Intelligence。若把它们当成三个互不相干的功能去写,您将付出四倍的工作量,结果反而更糟。

太长不看

  • 可交互小组件(iOS 17 及以上): 在小组件视图里放一个 Button(intent:)Toggle(isOn:intent:);系统会在您的小组件扩展中、脱离应用运行该 App Intent 的 perform(),随后重新加载时间线1
  • 控制中心控件(iOS 18 及以上):ControlWidgetButtonControlWidgetToggle 构建的 ControlWidget,背后同样是 App Intents,会出现在控制中心、锁定屏幕以及操作按钮上2
  • 实时活动(ActivityKit): ActivityAttributes 加上 ActivityConfiguration 驱动锁定屏幕与灵动岛;由服务器驱动的更新通过推送送达,其中的按钮同样是 App Intents3
  • iOS 26 改变的是呈现方式,而非模型本身: 小组件采用了 Liquid Glass 外观和着色渲染模式,但「App Intents 即界面」的架构与 iOS 18 相比并无变化4
  • 统一的事实在于:一个小组件按钮、一个控件、一句 Siri 指令、一次 Apple Intelligence 操作,都可以是同一个 App Intent。能力只需设计一次。

可交互小组件:不打开应用的按钮

机制很简单,值得把话说精确。您给小组件视图添加一个 SwiftUI ButtonToggle,但传入的不是动作闭包,而是一个 App Intent1

struct WaterWidgetView: View {
    let logged: Int

    var body: some View {
        VStack {
            Text("\(logged) glasses")
            Button(intent: LogWaterIntent()) {
                Label("Add", systemImage: "plus")
            }
        }
    }
}

用户点按时,系统会在您的小组件扩展内运行 LogWaterIntent.perform()。应用不会启动。intent 完成自己的工作(写入共享存储、更新计数),随后小组件时间线重新加载,显示结果。

Toggle 通过 SetValueIntent 以同样的方式运作,它会接收到用户刚刚设定的新开/关值:

struct ToggleReminderIntent: SetValueIntent {
    static let title: LocalizedStringResource = "Toggle Reminder"

    @Parameter(title: "Enabled")
    var value: Bool

    func perform() async throws -> some IntentResult {
        ReminderStore.shared.enabled = value
        return .result()
    }
}

小组件绑定 Toggle(isOn: store.enabled, intent: ToggleReminderIntent()),系统把 value 设为新状态,运行 perform(),然后重新加载。与按钮形态如出一辙,只是多了一个参数1

有两条约束决定了您在这里能做什么,两者都很容易踩坑。其一,perform() 运行在受限的小组件扩展环境中,执行预算十分紧张;它适合快速的状态变更,而非网络上传或繁重计算。其二,小组件反映状态,并不能随意做 UI 动画。您改变数据,时间线重新渲染;您无法运行自定义过渡动画。把交互设计成「翻转一个值,显示新值」,它就能跑通。一旦贪多,系统就会跟您较劲。

控制中心控件:同一个 intent,换一个房间

iOS 18 把控制中心、锁定屏幕和操作按钮向第三方控件开放,而其 API 与小组件有意保持平行2。一个控件就是一个 ControlWidget,其主体是接到某个 App Intent 上的 ControlWidgetButtonControlWidgetToggle

struct LogWaterControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "com.example.logWater") {
            ControlWidgetButton(action: LogWaterIntent()) {
                Label("Log Water", systemImage: "drop.fill")
            }
        }
    }
}

如果 LogWaterIntent 已经在驱动您的可交互小组件,那这个控件几乎是白送的:同一个 intent,换个外壳而已。这正是架构带来的回报。控件并不是该功能的第二份实现,而是您早已写好的那份功能的第二个挂载点。开关控件使用 SetValueIntent,方式与小组件开关完全一致,于是一项「静音」「开始会话」或「切换深色模式」的能力只需构建一次,就能出现在控制中心、锁定屏幕和操作按钮上,无需新增任何逻辑2

实时活动:会自我更新的界面

实时活动是第三处场所,也是活动部件最多的一处。ActivityAttributes 类型定义静态与动态内容,ActivityConfiguration 渲染锁定屏幕的呈现样式和灵动岛的各个区域,ActivityKit 则负责启动、更新和结束活动3。实时活动内部的按钮,依然是 App Intents,因此灵动岛里的「暂停」或「下一个」控件,运行的与小组件按钮属于同一类 intent。

让实时活动自成一门学问的,是其更新路径。本地更新的计时器很简单。但若活动是由服务器上发生的事件驱动的(一份外卖正在移动、一场比赛比分在变化),更新就通过推送进行:您注册 pushTokenUpdates,从后端发送 ActivityKit 推送负载,系统便会在您的应用根本没有运行的情况下更新锁定屏幕和灵动岛3。这确实强大,也确实极易出错,因为此时活动的正确性不再只取决于本地代码,而是取决于服务器契约、推送可靠性,以及一套过期时间策略。

iOS 26 到底改了什么

iOS 26 小组件的看点是呈现,而非架构。小组件采用了 Liquid Glass 材质,新增了着色渲染模式,从而契合系统全新的设计语言,并在操作系统重新着色内容的场景中正确染色4。这关系到小组件在主屏幕、锁定屏幕和待机显示中的观感,值得专门做一轮设计打磨。但它并未改变交互的工作方式。如果您早在 iOS 18 就构建了可交互小组件和控件,那么在 iOS 26 上要做的只是视觉焕新,而非重写。对于声称 iOS 26「引入」了可交互小组件的说法,请保持警惕;交互能力在 iOS 17 就已发布,控件在 iOS 18 发布,iOS 26 只是让它们看起来与系统其余部分浑然一体。

不过,Liquid Glass 带来的设计影响是实实在在的。小组件是系统叠加在墙纸之上、并经玻璃质感界面折射的内容,因此管辖 应用内 Liquid Glass 的那些规则在此同样适用:尊重「功能层与内容层」的分层,不要用玻璃读不出来的厚重自定义背景去对抗这种材质。

把架构讲清楚

这里有一个值得牢记的模型。一个 App Intent 是一项有名称、有类型、有描述的能力。iOS 给您提供了越来越多挂载这项能力的场所:

  • 小组件内的 ButtonToggle
  • 控制中心锁定屏幕操作按钮中的 ControlWidget
  • 实时活动及其灵动岛内的按钮。
  • 一句 Siri 指令和一个快捷指令操作。
  • Apple Intelligence 可代用户调用的一项操作5

这其中的每一处,都是同一个带 perform() 方法的 AppIntent 类型。功夫全在于把那项能力设计好:一个清晰的名称、带类型的参数、一个快速且幂等的 perform(),以及一个合理的结果。把这件事做好一次,各处界面就只是外壳。这正是我此前论述过的同一个观点——App Intents 是 通向您应用的真正 API,以及一个 iOS 应用如今暴露出的 三个界面:小组件界面不是您去构建的一项功能,而是您的各项能力一旦存在便会现身的场所。

何时不必费这个劲

小组件界面回报的是那些真正具备一目了然状态和快捷操作的应用:追踪器、计时器、开关、正在播放控件。它并不回报所有应用。

  • 没有一目了然的状态,就别做小组件。 如果不打开应用就没什么值得展示的东西,那小组件只是装饰,徒增维护负担和一个额外的扩展目标。
  • 繁重或缓慢的操作不该放进这里的 perform() 小组件和控件的执行环境是受限的。如果操作需要真正的时间或算力,按钮就应当打开应用、进入一个准备好的状态,而不是假装在扩展里把活干了。
  • 为并非「实时」的东西做实时活动。 实时活动面向的是有时限、正在主动变化的事件。把它当成常驻状态徽章来用,是对这一界面的误读,也是迅速登上系统活动预算黑名单的捷径。

真正的本事,不是学会三种 API,而是设计出值得挂载的 App Intents,然后把它们挂到用户本就在的地方:他们会查看的主屏幕、会上滑唤出的控制中心、已经在向他们展示信息的灵动岛。带着品味把能力构建一次,iOS 便会把各处界面免费奉上。



  1. Apple Developer,“Adding interactivity to widgets and Live Activities”。可交互小组件(iOS 17 及以上)使用以 AppIntent(开关则用 SetValueIntent)为后盾的 SwiftUI Button(intent:)Toggle(isOn:intent:);intent 的 perform() 在小组件扩展中运行,不启动应用,随后时间线重新加载以反映结果。 

  2. Apple Developer,“Creating controls to perform actions across the system”ControlWidget 协议。控件(iOS 18 及以上)由接到 App Intents 上的 ControlWidgetButtonControlWidgetToggle 构建,出现在控制中心、锁定屏幕以及操作按钮上。 

  3. Apple Developer,“ActivityKit”“Displaying live data with Live Activities”ActivityAttributesActivityConfiguration 定义并渲染锁定屏幕和灵动岛;ActivityKit 负责启动、更新和结束活动,而服务器驱动的更新通过 pushTokenUpdates 使用推送。 

  4. iOS 26 小组件渲染:小组件采用 Liquid Glass 材质和着色渲染模式,通过 WidgetRenderingMode\.widgetRenderingMode 环境值控制。交互模型(小组件和控件中的 App Intents)与 iOS 17(小组件)和 iOS 18(控件)相比并无变化。Apple,“WWDC 2025: the new software design”WidgetKit 文档。 

  5. Apple Developer,“App Intents”。同一个 AppIntent 类型,正是系统通过 Siri、快捷指令、聚焦搜索、小组件与控件界面,以及 Apple Intelligence 所暴露的那个单元。作者对跨界面模型的分析:App Intents 是 Apple 通向您应用的全新 APIApp Intents 2 与 iOS 26 的新增能力,以及 一个 iOS 应用的三个界面。 

相关文章

Live Activities是状态机,不是徽章

Return的Live Activity看起来像锁屏倒计时。它实际上是一个具有三条退出路径的五状态生命周期机器。生产代码与那些bug。

4 分钟阅读

Apple's Translation Framework: Free, On-Device, and Sharper Than It Looks

Apple's Translation framework: free on-device translation via translationPresentation and TranslationSession, plus the o…

8 分钟阅读

当维护者就是攻击者:jqwik 1.10.0

jqwik 1.10.0会在Maven输出中发出破坏性的提示注入字符串。ANSI转义会把它从人类视线中隐藏。维护者是有意加入的。

2 分钟阅读