Highlight
iOS 17 和 macOS Sonoma 引入了 StandBy、桌面 Widget 和 iPad 锁定屏幕等新场景,WidgetKit 新增了
containerBackground、contentMargins、widgetAccentable和containerBackgroundRemovable等 API,让 Widget 能自动适配不同环境的背景、边距和渲染风格。
核心内容
问题:Widget 曾经只有主屏幕一个归宿
iOS 14 引入 Widget 时,Widget 只有一个展示位置:主屏幕。开发者只需要考虑一种背景风格、一套边距规则、一种颜色渲染方式。
现在情况变了。iOS 17 带来了 StandBy 模式——iPhone 横置充电时,Widget 出现在深色全屏界面上。macOS Sonoma 允许 Widget 直接放在桌面上。iPad 的锁定屏幕也开始支持 Widget。
每个新场景对 Widget 的外观要求都不一样。StandBy 模式下系统会智能地为 Widget 着色,桌面 Widget 可能需要去掉背景色融入壁纸,而主屏幕 Widget 需要保持原有的鲜艳背景。一份代码适配所有环境,成了新难题。
Apple 的方案:环境感知的 Widget API
Apple 在 WidgetKit 中增加了三个核心能力:
- 容器背景(
containerBackground):让 Widget 知道自己的背景应该是什么风格,系统根据展示位置自动调整。 - 内容边距禁用(
contentMarginsDisabled):在需要无边距渲染时(如 StandBy),由开发者自行控制内边距。 - 强调色渲染(
widgetAccentable):在 StandBy 等深色智能着色模式下,标记哪些文本和元素应该被系统着色。
另外还有 containerBackgroundRemovable,让桌面 Widget 的背景可以被系统移除,实现”透明”效果融入壁纸。
这些 API 的共同思路是:开发者声明意图,系统根据运行环境决定具体渲染方式。
详细内容
禁用默认边距(02:08)
iOS 17 中,Widget 默认会带有系统添加的内容边距。对于需要完全控制布局的 Widget——比如用纯色或渐变铺满整个 Widget 的——这个默认边距会留下难看的留白。
contentMarginsDisabled() 关闭默认边距,然后通过 widgetContentMargins 环境值手动控制边距:
struct SafeAreasWidgetView: View {
@Environment(\.widgetContentMargins) var margins
var body: some View {
ZStack {
Color.blue
Group {
Color.lightBlue
Text("Hello, world!")
}
.padding(margins)
}
}
}
struct SafeAreasWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(...) {_ in
SafeAreasWidgetView()
}
.contentMarginsDisabled()
}
}
关键点:
contentMarginsDisabled()在WidgetConfiguration上调用,关闭系统默认边距。@Environment(\.widgetContentMargins)读取系统原本会应用的边距值,让你自行决定哪里加 padding。- 这种模式适合全幅背景色(
Color.blue)的 Widget——内层内容手动加边距,外层背景铺满。
容器背景:一套代码适配所有场景(03:19)
containerBackground(for:alignment:content:) 是 iOS 17 / macOS Sonoma 中最关键的 Widget 新 API。它替代了之前直接在 View 上设置背景的做法。
struct EmojiRangerWidgetEntryView: View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall:
ZStack {
AvatarView(entry.hero)
.widgetURL(entry.hero.url)
.foregroundColor(.white)
}
.containerBackground(for: .widget) {
Color.gameBackground
}
}
// additional cases
}
}
关键点:
containerBackground(for: .widget)告诉系统”这段内容是这个 Widget 的背景”。系统根据 Widget 的展示环境决定如何渲染它。- 在 StandBy 模式下,这个背景可能被移除或淡化;在桌面上,用户可以选择去掉背景让 Widget 融入壁纸。
- 不指定
for: .widget的普通背景(如ZStack中的Color.blue)不会受到系统智能调整。 @Environment(\.widgetFamily)用来区分小、中、大和 accessory 系列尺寸,同一个 Widget 入口在不同尺寸下走不同布局分支。
强调色:让 Widget 在 StandBy 中自动着色(03:48)
StandBy 模式下,系统会对 Widget 进行智能着色——将彩色内容转为适应深色环境的单色或双色调风格。但不是所有元素都应该被着色。widgetAccentable() 标记出那些需要被系统着色的文本:
var body: some View {
switch family {
case .accessoryRectangular:
HStack(alignment: .center, spacing: 0) {
VStack(alignment: .leading) {
Text(entry.hero.name)
.font(.headline)
.widgetAccentable()
Text("Level \(entry.hero.level)")
Text(entry.hero.fullHealthDate, style: .timer)
}.frame(maxWidth: .infinity, alignment: .leading)
Avatar(hero: entry.hero, includeBackground: false)
}
.containerBackground(for: .widget) {
Color.gameBackground
}
// additional cases
}
关键点:
widgetAccentable()标记文本元素,告诉系统在 StandBy 着色模式下优先着色这些内容。- 没有被标记的文本在着色模式下保持较淡的视觉效果,形成层次感。
accessoryRectangular是 Lock Screen / StandBy 中使用的矩形 Widget 尺寸——紧凑、信息密度高。- 这个修饰符仅在系统执行智能着色时才生效,常规主屏幕 Widget 不受影响。
可移除背景:让 Widget 融入 macOS 桌面(04:22)
macOS Sonoma 允许用户将 Widget 放在桌面上。用户可以选择”去掉 Widget 背景”,让它看起来像桌面的一部分。对于照片类 Widget,去掉背景可以让照片直接”浮”在壁纸之上。
struct PhotoWidget: Widget {
public var body: some WidgetConfiguration {
StaticConfiguration(...) { entry in
PhotoWidgetView(entry: entry)
}
.containerBackgroundRemovable(false)
}
}
关键点:
containerBackgroundRemovable(false)声明”我这个 Widget 的背景不能移除”。照片类 Widget 通常设为false,因为照片本身就是内容,去掉背景等于去掉 Widget。- 设为
true(默认值)时,用户在 macOS 桌面上可以选择去掉 Widget 背景。 - 这个 API 只在 macOS Sonoma 上有实际效果,但代码在所有平台上都可以写——系统自动处理平台差异。
核心启发
基于这组新 API,以下是几个可以动手的方向:
-
做一个 StandBy 专用时钟 Widget:利用
widgetAccentable()让时间数字在 StandBy 着色模式下保持突出,其他装饰元素淡化。用containerBackground(for: .widget)提供深色背景,让 Widget 在 StandBy 的深色环境中看起来是原生组件。实现入口:StaticConfiguration+accessoryRectangular布局。 -
升级现有 Widget 的桌面体验:如果你的 App 已有主屏幕 Widget,只需加两行代码就能让它在 macOS Sonoma 桌面上表现良好:把所有背景色移到
.containerBackground(for: .widget)中,给关键文本加.widgetAccentable()。不需要写任何平台判断代码。 -
做一个照片 Widget 适配桌面:用
containerBackgroundRemovable(false)阻止系统移除照片 Widget 的背景。用户把 Widget 拖到桌面后,照片无缝融入壁纸。结合PhotoPicker让用户选择展示的相册。 -
做一个”无边距”天气 Widget:用
contentMarginsDisabled()+widgetContentMargins手动控制边距,实现渐变背景铺满整个 Widget 区域的效果。适合做沉浸式天气展示——天空渐变从边缘到边缘。 -
为现有 Widget 添加 accessory 系列支持:如果你的 Widget 只支持
systemSmall/Medium/Large,加上accessoryRectangular和accessoryCircular两个 case,就能同时出现在 iPhone 的 Lock Screen 和 StandBy 上。复用数据模型,只改布局。
关联 Session
- What’s new in SwiftUI — SwiftUI 的最新更新,包括 Widget 相关的新修饰符和布局 API
- Explore enhancements to App Intents — App Intents 让 Widget 具备交互能力,了解最新的增强特性
- Build programmatic UI with Xcode Previews — 用 Xcode Previews 在不同尺寸和环境下预览 Widget
- Design for spatial computing — 了解如何为不同平台和展示环境设计一致的界面
评论
GitHub Issues · utterances