alarmkit by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill alarmkit在锁屏界面、灵动岛和 Apple Watch 上安排并显示醒目的闹钟和倒计时计时器。AlarmKit 要求 iOS 26+ / iPadOS 26+。闹钟会自动覆盖专注模式和静音模式。
AlarmKit 基于实时活动构建——每个闹钟都会创建一个系统管理的、具有模板化 UI 的实时活动。您通过配置 AlarmAttributes 和 AlarmPresentation 来配置其呈现方式,而不是构建自定义的小部件视图。
有关完整代码模式,包括授权、调度、倒计时计时器、贪睡处理和小组件设置,请参阅 references/alarmkit-patterns.md。
import AlarmKit
NSAlarmKitUsageDescription 并提供一个面向用户的描述字符串。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
AlarmManager.shared.requestAuthorization() 请求授权。AlarmPresentation(响铃、倒计时、暂停状态)。AlarmAttributes。AlarmManager.AlarmConfiguration(.alarm 或 .timer)。AlarmManager.shared.schedule(id:configuration:) 进行调度。alarmManager.alarmUpdates 观察状态变化。运行本文档末尾的审查清单。
AlarmKit 需要明确的用户授权。没有授权,闹钟会静默地调度失败。请在早期(例如,在应用引导阶段)请求授权,或者让 AlarmKit 在首次调度时自动提示。
let manager = AlarmManager.shared
// 显式请求授权
let state = try await manager.requestAuthorization()
guard state == .authorized else { return }
// 同步检查当前状态
let current = manager.authorizationState // .authorized, .denied, .notDetermined
// 观察授权变化
for await state in manager.authorizationUpdates {
switch state {
case .authorized: print("Alarms enabled")
case .denied: print("Alarms disabled")
case .notDetermined: break
@unknown default: break
}
}
| 功能 | 闹钟 (.alarm) | 计时器 (.timer) |
|---|---|---|
| 触发时间 | 特定时间(调度) | 持续时间结束后 |
| 倒计时 UI | 可选 | 始终显示 |
| 重复 | 支持(每周特定日期) | 不支持 |
| 用例 | 唤醒、定时提醒 | 烹饪、锻炼间隔 |
在需要按时钟时间触发时使用 .alarm(schedule:...)。在需要从现在起经过一段时间后触发时使用 .timer(duration:...)。
闹钟使用 Alarm.Schedule 来定义触发时间。
// 固定时间:在精确的 Date 触发(仅一次)
let fixed: Alarm.Schedule = .fixed(myDate)
// 相对一次性:在设备时区的早上 7:30 触发,不重复
let oneTime: Alarm.Schedule = .relative(.init(
time: .init(hour: 7, minute: 30),
repeats: .never
))
// 重复:在工作日早上 6:00 触发
let weekday: Alarm.Schedule = .relative(.init(
time: .init(hour: 6, minute: 0),
repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday])
))
let id = UUID()
let configuration = AlarmManager.AlarmConfiguration.alarm(
schedule: .relative(.init(
time: .init(hour: 7, minute: 0),
repeats: .never
)),
attributes: attributes,
stopIntent: StopAlarmIntent(alarmID: id.uuidString),
secondaryIntent: SnoozeIntent(alarmID: id.uuidString),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(
id: id,
configuration: configuration
)
cancel(id:)
|
scheduled --> countdown --> alerting
| | |
| pause(id:) stop(id:) / countdown(id:)
| |
| paused ----> countdown (via resume(id:))
|
cancel(id:) removes from system entirely
cancel(id:) —— 完全移除闹钟(任何状态)pause(id:) —— 暂停正在倒计时的闹钟resume(id:) —— 恢复已暂停的闹钟stop(id:) —— 停止正在响铃的闹钟countdown(id:) —— 从响铃状态重新开始倒计时(贪睡)计时器在持续时间结束后触发,并且始终显示倒计时 UI。使用 Alarm.CountdownDuration 来控制响铃前和响铃后的持续时间。
// 简单计时器:5分钟倒计时,无贪睡
let timerConfig = AlarmManager.AlarmConfiguration.timer(
duration: 300,
attributes: attributes,
stopIntent: StopTimerIntent(timerID: id.uuidString),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(
id: UUID(),
configuration: timerConfig
)
Alarm.CountdownDuration 控制可见的倒计时阶段:
preAlert —— 闹钟响铃前倒计时的秒数(主倒计时)
postAlert —— 闹钟响铃后用于重复/贪睡倒计时的秒数
let countdown = Alarm.CountdownDuration( preAlert: 600, // 响铃前 10 分钟倒计时 postAlert: 300 // 如果用户点击“重复”,则进行 5 分钟贪睡倒计时 )
let config = AlarmManager.AlarmConfiguration( countdownDuration: countdown, schedule: .relative(.init( time: .init(hour: 8, minute: 0), repeats: .never )), attributes: attributes, stopIntent: stopIntent, secondaryIntent: snoozeIntent, sound: .default )
每个 Alarm 都有一个 state 属性,反映其当前的生命周期位置。
| 状态 | 含义 |
|---|---|
.scheduled | 等待触发(闹钟模式)或等待开始倒计时 |
.countdown | 正在积极倒计时(计时器或响铃前阶段) |
.paused | 倒计时被用户或应用暂停 |
.alerting | 闹钟正在响铃——播放声音,UI 突出显示 |
let manager = AlarmManager.shared
// 获取所有当前闹钟
let alarms = manager.alarms
// 作为异步序列观察变化
for await updatedAlarms in manager.alarmUpdates {
for alarm in updatedAlarms {
switch alarm.state {
case .scheduled: print("\(alarm.id) waiting")
case .countdown: print("\(alarm.id) counting down")
case .paused: print("\(alarm.id) paused")
case .alerting: print("\(alarm.id) alerting!")
@unknown default: break
}
}
}
从 alarmUpdates 中消失的闹钟表示已被取消或完全停止,系统不再跟踪。
AlarmAttributes 遵循 ActivityAttributes 协议,并定义了闹钟实时活动的静态数据。它是泛型的,其 Metadata 类型需遵循 AlarmMetadata 协议。
定义每个闹钟状态的 UI 内容。系统使用此数据渲染一个模板化的实时活动——您无需为闹钟本身构建自定义的 SwiftUI 视图。
// 响铃状态(必需)——闹钟响铃时显示
let alert = AlarmPresentation.Alert(
title: "Wake Up",
secondaryButton: AlarmButton(
text: "Snooze",
textColor: .white,
systemImageName: "bell.slash"
),
secondaryButtonBehavior: .countdown // 贪睡会重新开始倒计时
)
// 倒计时状态(可选)——在响铃前倒计时期间显示
let countdown = AlarmPresentation.Countdown(
title: "Morning Alarm",
pauseButton: AlarmButton(
text: "Pause",
textColor: .orange,
systemImageName: "pause.fill"
)
)
// 暂停状态(可选)——倒计时暂停时显示
let paused = AlarmPresentation.Paused(
title: "Paused",
resumeButton: AlarmButton(
text: "Resume",
textColor: .green,
systemImageName: "play.fill"
)
)
let presentation = AlarmPresentation(
alert: alert,
countdown: countdown,
paused: paused
)
struct CookingMetadata: AlarmMetadata {
var recipeName: String
var stepNumber: Int
}
let attributes = AlarmAttributes(
presentation: presentation,
metadata: CookingMetadata(recipeName: "Pasta", stepNumber: 3),
tintColor: .blue
)
AlarmPresentationState 是系统管理的闹钟实时活动的 ContentState。它包含闹钟 ID 和一个 Mode 枚举:
.alert(Alert) —— 闹钟正在响铃,包含预定时间.countdown(Countdown) —— 正在积极倒计时,包含触发日期和持续时间.paused(Paused) —— 倒计时已暂停,包含已用时间和总持续时间小组件扩展读取 AlarmPresentationState.mode,以决定在灵动岛和锁屏界面上为非响铃状态渲染哪个 UI。
AlarmButton 定义了闹钟 UI 中操作按钮的外观。
let stopButton = AlarmButton(
text: "Stop",
textColor: .red,
systemImageName: "stop.fill"
)
let snoozeButton = AlarmButton(
text: "Snooze",
textColor: .white,
systemImageName: "bell.slash"
)
响铃 UI 上的次要按钮有两种行为:
| 行为 | 效果 |
|---|---|
.countdown | 使用 postAlert 持续时间重新开始倒计时(贪睡) |
.custom | 触发 secondaryIntent(例如,打开应用) |
AlarmKit 闹钟会自动作为实时活动出现在 iPhone 的锁屏界面和灵动岛上,以及 Apple Watch 的智能叠放中。系统管理响铃 UI。对于倒计时和暂停状态,需要添加一个读取 AlarmAttributes 和 AlarmPresentationState 的小部件扩展。
如果您的闹钟使用倒计时呈现,则必须添加小部件扩展。没有它,系统可能会意外地解除闹钟。
struct AlarmWidgetBundle: WidgetBundle {
var body: some Widget {
AlarmActivityWidget()
}
}
struct AlarmActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AlarmAttributes<CookingMetadata>.self) { context in
// 倒计时/暂停状态的锁屏呈现
AlarmLockScreenView(context: context)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.center) {
Text(context.attributes.presentation.alert.title)
}
DynamicIslandExpandedRegion(.bottom) {
// 根据模式显示倒计时或暂停信息
AlarmExpandedView(state: context.state)
}
} compactLeading: {
Image(systemName: "alarm.fill")
} compactTrailing: {
AlarmCompactTrailing(state: context.state)
} minimal: {
Image(systemName: "alarm.fill")
}
}
}
}
不要: 忘记在 Info.plist 中添加 NSAlarmKitUsageDescription。要: 添加一个描述性的用途字符串。没有它,AlarmKit 根本无法调度闹钟。
不要: 跳过授权并假设闹钟会成功调度。要: 尽早调用 requestAuthorization() 并优雅地处理 .denied 状态。
不要: 在需要重复调度时使用 .timer。要: 对于重复闹钟,使用带有 .weekly([...]) 的 .alarm。计时器是一次性的。
不要: 在使用倒计时呈现时省略小部件扩展。要: 添加小部件扩展目标。AlarmKit 需要它来呈现倒计时/暂停状态的实时活动 UI。原因: 没有小部件扩展,系统可能会在闹钟响铃前将其解除。
不要: 忽略 alarmUpdates 并手动跟踪闹钟状态。要: 观察 alarmManager.alarmUpdates 以与系统保持同步。原因: 闹钟状态可能在您的应用处于后台时发生变化。
不要: 忘记提供 stopIntent —— 实际上它不能为 nil。要: 始终为停止操作提供一个 LiveActivityIntent,以便按钮执行清理工作。
不要: 在 AlarmMetadata 中存储大量数据。它会与实时活动一起序列化。要: 保持元数据轻量。将大量数据存储在您的应用中,并通过 ID 引用。
不要: 使用 AlarmPresentation.Alert 上已弃用的 stopButton 参数。要: 使用当前的 init(title:secondaryButton:secondaryButtonBehavior:) 初始化器。
NSAlarmKitUsageDescription 且字符串非空.denied 状态AlarmPresentation 覆盖了所有相关状态(响铃、倒计时、暂停)AlarmAttributes 的元数据类型遵循 AlarmMetadata 协议alarmUpdates 异步序列以跟踪状态变化stopIntent 和 secondaryIntent 是有效的 LiveActivityIntent 实现.countdown 行为),则在 CountdownDuration 上设置了 postAlert 持续时间AlarmAttributes 上设置了色调颜色以区别于其他应用AlarmManager.AlarmError.maximumLimitReached 进行了错误处理references/alarmkit-patterns.md每周安装量
337
代码仓库
GitHub 星标数
269
首次出现
2026年3月8日
安全审计
安装于
codex334
opencode331
github-copilot331
amp331
cline331
kimi-cli331
Schedule prominent alarms and countdown timers that surface on the Lock Screen, Dynamic Island, and Apple Watch. AlarmKit requires iOS 26+ / iPadOS 26+. Alarms override Focus and Silent mode automatically.
AlarmKit builds on Live Activities -- every alarm creates a system-managed Live Activity with templated UI. You configure the presentation via AlarmAttributes and AlarmPresentation rather than building custom widget views.
See references/alarmkit-patterns.md for complete code patterns including authorization, scheduling, countdown timers, snooze handling, and widget setup.
import AlarmKit
NSAlarmKitUsageDescription to Info.plist with a user-facing string.AlarmManager.shared.requestAuthorization().AlarmPresentation (alert, countdown, paused states).AlarmAttributes with the presentation, optional metadata, and tint color.AlarmManager.AlarmConfiguration (.alarm or .timer).AlarmManager.shared.schedule(id:configuration:).alarmManager.alarmUpdates.Run through the Review Checklist at the end of this document.
AlarmKit requires explicit user authorization. Without it, alarms silently fail to schedule. Request early (e.g., at onboarding) or let AlarmKit prompt automatically on first schedule.
let manager = AlarmManager.shared
// Request authorization explicitly
let state = try await manager.requestAuthorization()
guard state == .authorized else { return }
// Check current state synchronously
let current = manager.authorizationState // .authorized, .denied, .notDetermined
// Observe authorization changes
for await state in manager.authorizationUpdates {
switch state {
case .authorized: print("Alarms enabled")
case .denied: print("Alarms disabled")
case .notDetermined: break
@unknown default: break
}
}
| Feature | Alarm (.alarm) | Timer (.timer) |
|---|---|---|
| Fires at | Specific time (schedule) | After duration elapses |
| Countdown UI | Optional | Always shown |
| Recurring | Yes (weekly days) | No |
| Use case | Wake-up, scheduled reminders | Cooking, workout intervals |
Use .alarm(schedule:...) when firing at a clock time. Use .timer(duration:...) when firing after a duration from now.
Alarms use Alarm.Schedule to define when they fire.
// Fixed: fire at an exact Date (one-time only)
let fixed: Alarm.Schedule = .fixed(myDate)
// Relative one-time: fire at 7:30 AM in device time zone, no repeat
let oneTime: Alarm.Schedule = .relative(.init(
time: .init(hour: 7, minute: 30),
repeats: .never
))
// Recurring: fire at 6:00 AM on weekdays
let weekday: Alarm.Schedule = .relative(.init(
time: .init(hour: 6, minute: 0),
repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday])
))
let id = UUID()
let configuration = AlarmManager.AlarmConfiguration.alarm(
schedule: .relative(.init(
time: .init(hour: 7, minute: 0),
repeats: .never
)),
attributes: attributes,
stopIntent: StopAlarmIntent(alarmID: id.uuidString),
secondaryIntent: SnoozeIntent(alarmID: id.uuidString),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(
id: id,
configuration: configuration
)
cancel(id:)
|
scheduled --> countdown --> alerting
| | |
| pause(id:) stop(id:) / countdown(id:)
| |
| paused ----> countdown (via resume(id:))
|
cancel(id:) removes from system entirely
cancel(id:) -- remove the alarm completely (any state)pause(id:) -- pause a counting-down alarmresume(id:) -- resume a paused alarmstop(id:) -- stop an alerting alarmcountdown(id:) -- restart countdown from alerting state (snooze)Timers fire after a duration and always show a countdown UI. Use Alarm.CountdownDuration to control pre-alert and post-alert durations.
// Simple timer: 5-minute countdown, no snooze
let timerConfig = AlarmManager.AlarmConfiguration.timer(
duration: 300,
attributes: attributes,
stopIntent: StopTimerIntent(timerID: id.uuidString),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(
id: UUID(),
configuration: timerConfig
)
Alarm.CountdownDuration controls the visible countdown phases:
preAlert -- seconds to count down before the alarm fires (the main countdown)
postAlert -- seconds for a repeat/snooze countdown after the alarm fires
let countdown = Alarm.CountdownDuration( preAlert: 600, // 10-minute countdown before alert postAlert: 300 // 5-minute snooze countdown if user taps Repeat )
let config = AlarmManager.AlarmConfiguration( countdownDuration: countdown, schedule: .relative(.init( time: .init(hour: 8, minute: 0), repeats: .never )), attributes: attributes, stopIntent: stopIntent, secondaryIntent: snoozeIntent, sound: .default )
Each Alarm has a state property reflecting its current lifecycle position.
| State | Meaning |
|---|---|
.scheduled | Waiting to fire (alarm mode) or waiting to start countdown |
.countdown | Actively counting down (timer or pre-alert phase) |
.paused | Countdown paused by user or app |
.alerting | Alarm is firing -- sound playing, UI prominent |
let manager = AlarmManager.shared
// Get all current alarms
let alarms = manager.alarms
// Observe changes as an async sequence
for await updatedAlarms in manager.alarmUpdates {
for alarm in updatedAlarms {
switch alarm.state {
case .scheduled: print("\(alarm.id) waiting")
case .countdown: print("\(alarm.id) counting down")
case .paused: print("\(alarm.id) paused")
case .alerting: print("\(alarm.id) alerting!")
@unknown default: break
}
}
}
An alarm that disappears from alarmUpdates has been cancelled or fully stopped and is no longer tracked by the system.
AlarmAttributes conforms to ActivityAttributes and defines the static data for the alarm's Live Activity. It is generic over a Metadata type conforming to AlarmMetadata.
Defines the UI content for each alarm state. The system renders a templated Live Activity using this data -- you do not build custom SwiftUI views for the alarm itself.
// Alert state (required) -- shown when alarm is firing
let alert = AlarmPresentation.Alert(
title: "Wake Up",
secondaryButton: AlarmButton(
text: "Snooze",
textColor: .white,
systemImageName: "bell.slash"
),
secondaryButtonBehavior: .countdown // snooze restarts countdown
)
// Countdown state (optional) -- shown during pre-alert countdown
let countdown = AlarmPresentation.Countdown(
title: "Morning Alarm",
pauseButton: AlarmButton(
text: "Pause",
textColor: .orange,
systemImageName: "pause.fill"
)
)
// Paused state (optional) -- shown when countdown is paused
let paused = AlarmPresentation.Paused(
title: "Paused",
resumeButton: AlarmButton(
text: "Resume",
textColor: .green,
systemImageName: "play.fill"
)
)
let presentation = AlarmPresentation(
alert: alert,
countdown: countdown,
paused: paused
)
struct CookingMetadata: AlarmMetadata {
var recipeName: String
var stepNumber: Int
}
let attributes = AlarmAttributes(
presentation: presentation,
metadata: CookingMetadata(recipeName: "Pasta", stepNumber: 3),
tintColor: .blue
)
AlarmPresentationState is the system-managed ContentState of the alarm Live Activity. It contains the alarm ID and a Mode enum:
.alert(Alert) -- alarm is firing, includes the scheduled time.countdown(Countdown) -- actively counting down, includes fire date and durations.paused(Paused) -- countdown paused, includes elapsed and total durationsThe widget extension reads AlarmPresentationState.mode to decide which UI to render in the Dynamic Island and Lock Screen for non-alerting states.
AlarmButton defines the appearance of action buttons in the alarm UI.
let stopButton = AlarmButton(
text: "Stop",
textColor: .red,
systemImageName: "stop.fill"
)
let snoozeButton = AlarmButton(
text: "Snooze",
textColor: .white,
systemImageName: "bell.slash"
)
The secondary button on the alert UI has two behaviors:
| Behavior | Effect |
|---|---|
.countdown | Restarts a countdown using postAlert duration (snooze) |
.custom | Triggers the secondaryIntent (e.g., open app) |
AlarmKit alarms automatically appear as Live Activities on the Lock Screen and Dynamic Island on iPhone, and in the Smart Stack on Apple Watch. The system manages the alerting UI. For countdown and paused states, add a widget extension that reads AlarmAttributes and AlarmPresentationState.
A widget extension is required if your alarm uses countdown presentation. Without it, the system may dismiss alarms unexpectedly.
struct AlarmWidgetBundle: WidgetBundle {
var body: some Widget {
AlarmActivityWidget()
}
}
struct AlarmActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AlarmAttributes<CookingMetadata>.self) { context in
// Lock Screen presentation for countdown/paused states
AlarmLockScreenView(context: context)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.center) {
Text(context.attributes.presentation.alert.title)
}
DynamicIslandExpandedRegion(.bottom) {
// Show countdown or paused info based on mode
AlarmExpandedView(state: context.state)
}
} compactLeading: {
Image(systemName: "alarm.fill")
} compactTrailing: {
AlarmCompactTrailing(state: context.state)
} minimal: {
Image(systemName: "alarm.fill")
}
}
}
}
DON'T: Forget NSAlarmKitUsageDescription in Info.plist. DO: Add a descriptive usage string. Without it, AlarmKit cannot schedule alarms at all.
DON'T: Skip authorization and assume alarms will schedule. DO: Call requestAuthorization() early and handle .denied gracefully.
DON'T: Use .timer when you need a recurring schedule. DO: Use .alarm with .weekly([...]) for recurring alarms. Timers are one-shot.
DON'T: Omit the widget extension when using countdown presentation. DO: Add a widget extension target. AlarmKit requires it for countdown/paused Live Activity UI. Why: Without a widget extension, the system may dismiss alarms before they alert.
DON'T: Ignore alarmUpdates and track alarm state manually. DO: Observe alarmManager.alarmUpdates to stay synchronized with the system. Why: Alarm state can change while your app is backgrounded.
DON'T: Forget to provide a stopIntent -- it cannot be nil in practice. DO: Always provide a LiveActivityIntent for stop so the button performs cleanup.
DON'T: Store large data in AlarmMetadata. It is serialized with the Live Activity. DO: Keep metadata lightweight. Store large data in your app and reference by ID.
DON'T: Use deprecated stopButton parameter on AlarmPresentation.Alert. DO: Use the current init(title:secondaryButton:secondaryButtonBehavior:) initializer.
NSAlarmKitUsageDescription present in Info.plist with non-empty string.denied state handled in UIAlarmPresentation covers all relevant states (alert, countdown, paused)AlarmAttributes metadata type conforms to AlarmMetadataalarmUpdates async sequence observed to track state changesstopIntent and secondaryIntent are valid LiveActivityIntent implementationsreferences/alarmkit-patterns.mdWeekly Installs
337
Repository
GitHub Stars
269
First Seen
Mar 8, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex334
opencode331
github-copilot331
amp331
cline331
kimi-cli331
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
postAlert duration set on CountdownDuration if snooze (.countdown behavior) is usedAlarmAttributes to differentiate from other appsAlarmManager.AlarmError.maximumLimitReached