Highlight
iOS 26 通过 AlarmKit 把”会破静音、会穿透专注模式”的闹钟能力开放给第三方 App,配合 Live Activity 在锁屏和灵动岛展示倒计时,并允许通过 App Intents 自定义按钮行为。
核心内容
第三方 App 一直被一个边界卡住:本地通知会被静音和专注模式压住,到点了用户根本听不见。做菜计时器、起床闹钟、长跑配速提醒这类需要”必须响”的场景,开发者要么劝用户关静音,要么走推送+音频后台播放的灰色路径。系统级的闹钟能力,过去只有 Apple 自家的”时钟”应用能用。
iOS 26 的 AlarmKit 把这道墙拆了。框架提供和系统闹钟一样的优先级——响铃时穿透静音和当前 Focus,配合锁屏、灵动岛、StandBy 和 Apple Watch 的展示。开发者在 Info.plist 里声明 NSAlarmKitUsageDescription,用户授权之后,App 就可以用 AlarmManager.shared.schedule(id:configuration:) 调度闹钟。一个闹钟由四部分组成:倒计时时长、调度策略(固定时间或相对时间循环)、外观配置(标题、按钮、颜色)和可选的自定义声音。Live Activity 不是装饰,是必需品——只要闹钟带倒计时功能,App 就必须实现对应的 Live Activity,否则系统会用兜底的系统外观。
详细内容
调度一个闹钟之前要先检查授权状态。AlarmManager.shared.authorizationState 返回 .notDetermined / .authorized / .denied 三种值(02:41)。
import AlarmKit
func checkAuthorization() {
switch AlarmManager.shared.authorizationState {
case .notDetermined:
// Manually request authorization
case .authorized:
// Proceed with scheduling
case .denied:
// Inform status is not authorized
}
}
关键点:
AlarmManager.shared是单例入口,所有调度、查询、取消都走它。- 如果状态是
.notDetermined,可以调用requestAuthorization主动弹窗;也可以等首次schedule时由系统自动弹。 .denied状态下要在 UI 上明确告诉用户闹钟不会响——别让用户以为设上了就万事大吉。
倒计时时长由 Alarm.CountdownDuration 描述,分 preAlert 和 postAlert 两段(04:08)。
let countdownDuration = Alarm.CountdownDuration(preAlert: (10 * 60), postAlert: (5 * 60))
关键点:
preAlert是首次响铃前的倒计时,单位秒。10 分钟煎牛排,写10 * 60。postAlert是用户按”重复”或”贪睡”后再次响铃前的倒计时。- 两个值都可选,纯定点闹钟(不带倒计时)可以省略。
调度策略有两种。固定调度 Alarm.Schedule.fixed(_:) 接收一个绝对 Date,不随时区变化(04:40)。
let keynoteDateComponents = DateComponents(
calendar: .current,
year: 2025, month: 6, day: 9,
hour: 9, minute: 41)
let keynoteDate = Calendar.current.date(from: keynoteDateComponents)!
let scheduleFixed = Alarm.Schedule.fixed(keynoteDate)
相对调度 Alarm.Schedule.Relative 接收时间和循环规则,会跟随设备时区漂移(05:13)。
let time = Alarm.Schedule.Relative.Time(hour: 7, minute: 0)
let recurrence = Alarm.Schedule.Relative.Recurrence.weekly([
.monday, .wednesday, .friday
])
let schedule = Alarm.Schedule.Relative(time: time, repeats: recurrence)
关键点:
- 出差闹钟用
fixed——飞到东京也按出发地的绝对时间响。 - 工作日早起闹钟用
Relative——跨时区会自动调整成当地 7 点。 Recurrence.weekly([.monday, ...])用Set表达星期几循环,符合直觉。
外观配置围绕 AlarmAttributes 和 AlarmConfiguration 两层结构。最小化的”只显示弹窗+一个解除按钮”配置如下(05:43):
func scheduleAlarm() async throws {
typealias AlarmConfiguration = AlarmManager.AlarmConfiguration<CookingData>
let id = UUID()
let duration = Alarm.CountdownDuration(preAlert: (10 * 60), postAlert: (5 * 60))
let stopButton = AlarmButton(
text: "Dismiss",
textColor: .white,
systemImageName: "stop.circle")
let alertPresentation = AlarmPresentation.Alert(
title: "Food Ready!",
stopButton: stopButton)
let attributes = AlarmAttributes<CookingData>(
presentation: AlarmPresentation(alert: alertPresentation),
tintColor: Color.green)
let alarmConfiguration = AlarmConfiguration(
countdownDuration: duration,
attributes: attributes)
try await AlarmManager.shared.schedule(id: id, configuration: alarmConfiguration)
}
关键点:
id: UUID由 App 生成并保存——后续取消、查询闹钟都靠它。AlarmButton三件套:文字、文字颜色、SF Symbol 名。SF Symbol 用于灵动岛紧凑模式。AlarmAttributes<CookingData>的泛型参数是自定义元数据类型,传Never也行,但带元数据更灵活。tintColor控制系统外观下倒计时主色调。
如果想要”重复”按钮触发再次倒计时,加 secondaryButton 并把 secondaryButtonBehavior 设成 .countdown(07:17)。如果想要按钮跳进 App 里执行自定义逻辑,把行为设成 .custom 并提供一个 LiveActivityIntent(14:09):
let secondaryIntent = OpenInApp(alarmID: id.uuidString)
let openButton = AlarmButton(
text: "Open",
textColor: .white,
systemImageName: "arrow.right.circle.fill")
let alertPresentation = AlarmPresentation.Alert(
title: "Food Ready!",
stopButton: stopButton,
secondaryButton: openButton,
secondaryButtonBehavior: .custom)
// ...
let alarmConfiguration = AlarmConfiguration(
countdownDuration: duration,
attributes: attributes,
secondaryIntent: secondaryIntent)
public struct OpenInApp: LiveActivityIntent {
public func perform() async throws -> some IntentResult { .result() }
public static var title: LocalizedStringResource = "Open App"
public static var openAppWhenRun = true
@Parameter(title: "alarmID")
public var alarmID: String
public init(alarmID: String) { self.alarmID = alarmID }
public init() { self.alarmID = "" }
}
关键点:
secondaryButtonBehavior: .custom配secondaryIntent才能在锁屏直接执行 App 逻辑。LiveActivityIntent必须有无参init()——系统反序列化时用得到。openAppWhenRun = true让按钮在执行 Intent 之后把 App 推到前台。alarmID通过@Parameter传进来,让 Intent 知道是哪只闹钟触发的。
倒计时本身需要一个 Live Activity 来渲染。ActivityConfiguration 的泛型参数指向 AlarmAttributes<CookingData>,根据 context.state.mode 渲染 .countdown / .paused / .alert 三种状态(09:15):
struct AlarmLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AlarmAttributes<CookingData>.self) { context in
switch context.state.mode {
case .countdown:
countdownView(context)
case .paused:
pausedView(context)
case .alert:
alertView(context)
}
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) { leadingView(context) }
DynamicIslandExpandedRegion(.trailing) { trailingView(context) }
} compactLeading: { compactLeadingView(context) }
compactTrailing: { compactTrailingView(context) }
minimal: { minimalView(context) }
}
}
}
关键点:
- AlarmKit 的 Live Activity 加在 App 的 Widget Extension 里,和普通 ActivityKit 写法一致。
context.state.mode是系统注入的状态机,不需要自己管。- Dynamic Island 的紧凑、最小、展开三种尺寸都要给视图,否则系统会显示空白。
- 自定义元数据通过
context.attributes.metadata取,可以驱动 SF Symbol 切换(如煎/烤切换图标)。
核心启发
-
做什么:在做菜/烘焙类 App 里加 AlarmKit 倒计时,替代
UNUserNotificationCenter的本地通知。- 为什么值得做:本地通知遇到静音和 Focus 就哑火,而 AlarmKit 会破静音;牛排煎过头比 App 弹个气泡严重得多。
- 怎么开始:在 Info.plist 加
NSAlarmKitUsageDescription,文案写”用于烹饪计时器”;调度时填preAlert为菜谱步骤时长,postAlert留 1–2 分钟兜底。
-
做什么:跨时区差旅 App 用
Alarm.Schedule.fixed设登机闹钟。- 为什么值得做:相对调度跨时区会漂移,登机时间是绝对时间,不能漂;用固定调度才能保证”飞到东京也按出发地写下的时间响”。
- 怎么开始:登机时间用航班所在地时区构造
DateComponents,转成Date后传给Alarm.Schedule.fixed(_:);闹钟的id存进行程数据库,便于改签时cancel+ 重新schedule。
-
做什么:长跑/番茄钟 App 用
secondaryButtonBehavior: .custom让锁屏按钮直接执行下一步。- 为什么值得做:用户跑步时湿手解锁费劲,锁屏按钮直接进入下一组训练比”打开 App 找按钮”流畅得多。
- 怎么开始:定义
LiveActivityIntent,把alarmID作为@Parameter传进去;perform()里读训练数据库里的下一组配速,重新调度新闹钟。
-
做什么:起床闹钟 App 用相对调度配合工作日
Recurrence.weekly一次性设好周一到周五。- 为什么值得做:用户最讨厌每天手动调闹钟;一次设定按周循环,跨时区出差也自动按当地 7 点响。
- 怎么开始:用
Alarm.Schedule.Relative.Time(hour:minute:)写时分,用.weekly([.monday, .tuesday, ...])列举工作日;周末规则单独再起一个闹钟。
关联 Session
- Meet ActivityKit — Live Activity 框架入门,AlarmKit 倒计时 UI 直接复用它。
- Bring widgets to new places — Widget 与 Live Activity 在 iOS 26 新增的展示位置。
- Explore enhancements to App Intents —
LiveActivityIntent是 App Intents 家族的一员,自定义按钮行为靠它。 - What’s new in widgets — 锁屏、灵动岛、StandBy 的 Widget 更新,关系到闹钟外观。
评论
GitHub Issues · utterances