Loading...
Loading...
Compare original and translation side by side
AlarmManagerAlarmManagerimport AlarmKit
let manager = AlarmManager.sharedimport AlarmKit
let manager = AlarmManager.sharedstruct Alarm {
var id: UUID
var schedule: Schedule?
var countdownDuration: CountdownDuration?
var state: AlarmState
}struct Alarm {
var id: UUID
var schedule: Schedule?
var countdownDuration: CountdownDuration?
var state: AlarmState
}struct AlarmPresentation {
var alert: Alert // Required: shown when alarm fires
var countdown: Countdown? // Optional: shown during countdown
var paused: Paused? // Optional: shown when paused
}struct AlarmPresentation {
var alert: Alert // 必填:闹钟触发时显示
var countdown: Countdown? // 可选:倒计时过程中显示
var paused: Paused? // 可选:暂停时显示
}struct AlarmAttributes<Metadata: AlarmMetadata> {
var presentation: AlarmPresentation
var metadata: Metadata
var tintColor: Color
}struct AlarmAttributes<Metadata: AlarmMetadata> {
var presentation: AlarmPresentation
var metadata: Metadata
var tintColor: Color
}struct RecipeMetadata: AlarmMetadata {
let recipeName: String
let cookingStep: String
}struct RecipeMetadata: AlarmMetadata {
let recipeName: String
let cookingStep: String
}NSAlarmKitUsageDescriptionNSAlarmKitUsageDescriptionfunc requestAlarmAuthorization() async -> Bool {
do {
let state = try await AlarmManager.shared.requestAuthorization()
return state == .authorized
} catch {
print("Authorization error: \(error)")
return false
}
}func requestAlarmAuthorization() async -> Bool {
do {
let state = try await AlarmManager.shared.requestAuthorization()
return state == .authorized
} catch {
print("Authorization error: \(error)")
return false
}
}authorizationStateauthorizationStatuslet state = await AlarmManager.shared.authorizationState
// .authorized | .denied | .notDeterminedauthorizationStateauthorizationStatuslet state = await AlarmManager.shared.authorizationState
// .authorized | .denied | .notDeterminedfor await authState in AlarmManager.shared.authorizationUpdates {
switch authState {
case .authorized: enableAlarmUI()
case .denied: showPermissionPrompt()
case .notDetermined: break
@unknown default: break
}
}for await authState in AlarmManager.shared.authorizationUpdates {
switch authState {
case .authorized: enableAlarmUI()
case .denied: showPermissionPrompt()
case .notDetermined: break
@unknown default: break
}
}UUIDAlarmManager.AlarmConfigurationschedule(id:configuration:)UUIDAlarmManager.AlarmConfigurationschedule(id:configuration:)let id = UUID()
let time = Alarm.Schedule.Relative.Time(hour: 7, minute: 30)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: .never
))
let alert = AlarmPresentation.Alert(
title: "Wake Up",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown
)
struct EmptyMetadata: AlarmMetadata {}
let config = AlarmManager.AlarmConfiguration(
countdownDuration: nil,
schedule: schedule,
attributes: AlarmAttributes(
presentation: AlarmPresentation(alert: alert),
metadata: EmptyMetadata(),
tintColor: .blue
),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(id: id, configuration: config)let id = UUID()
let time = Alarm.Schedule.Relative.Time(hour: 7, minute: 30)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: .never
))
let alert = AlarmPresentation.Alert(
title: "Wake Up",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown
)
struct EmptyMetadata: AlarmMetadata {}
let config = AlarmManager.AlarmConfiguration(
countdownDuration: nil,
schedule: schedule,
attributes: AlarmAttributes(
presentation: AlarmPresentation(alert: alert),
metadata: EmptyMetadata(),
tintColor: .blue
),
sound: .default
)
let alarm = try await AlarmManager.shared.schedule(id: id, configuration: config).weekly(Array(weekdays))let time = Alarm.Schedule.Relative.Time(hour: 6, minute: 0)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday])
)).weekly(Array(weekdays))let time = Alarm.Schedule.Relative.Time(hour: 6, minute: 0)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday])
))schedule: nilcountdownDurationpreAlertlet countdown = Alarm.CountdownDuration(
preAlert: 300, // 5 minutes
postAlert: 10 // Optional post-alert snooze window
)
let config = AlarmManager.AlarmConfiguration(
countdownDuration: countdown,
schedule: nil,
attributes: attributes,
sound: .default
)AlarmPresentation.countdownschedule: nilpreAlertcountdownDurationlet countdown = Alarm.CountdownDuration(
preAlert: 300, // 5分钟
postAlert: 10 // 可选的提醒后贪睡窗口
)
let config = AlarmManager.AlarmConfiguration(
countdownDuration: countdown,
schedule: nil,
attributes: attributes,
sound: .default
)AlarmPresentation.countdownCountdownDuration.postAlert.snoozeButtonlet alert = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown // Starts post-alert countdown
)
let countdownDuration = Alarm.CountdownDuration(
preAlert: nil,
postAlert: 9 * 60 // 9-minute snooze
)CountdownDuration.postAlert.snoozeButtonlet alert = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown // 启动提醒后倒计时
)
let countdownDuration = Alarm.CountdownDuration(
preAlert: nil,
postAlert: 9 * 60 // 9分钟贪睡
)// Minimal
let basic = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton
)
// With custom button labels
let custom = AlarmPresentation.Alert(
title: "Medication Reminder",
stopButton: AlarmButton(label: "Taken"),
secondaryButton: AlarmButton(label: "Remind Later"),
secondaryButtonBehavior: .countdown
)
// With open-app action
let openApp = AlarmPresentation.Alert(
title: "Workout Time",
stopButton: .stopButton,
secondaryButton: .openAppButton,
secondaryButtonBehavior: .custom
)// 基础版
let basic = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton
)
// 自定义按钮标签
let custom = AlarmPresentation.Alert(
title: "Medication Reminder",
stopButton: AlarmButton(label: "Taken"),
secondaryButton: AlarmButton(label: "Remind Later"),
secondaryButtonBehavior: .countdown
)
// 带打开应用操作
let openApp = AlarmPresentation.Alert(
title: "Workout Time",
stopButton: .stopButton,
secondaryButton: .openAppButton,
secondaryButtonBehavior: .custom
)countdownDuration.preAlertlet countdown = AlarmPresentation.Countdown(
title: "Timer Running",
pauseButton: .pauseButton
)countdownDuration.preAlertlet countdown = AlarmPresentation.Countdown(
title: "Timer Running",
pauseButton: .pauseButton
)let paused = AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)let paused = AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)let presentation = AlarmPresentation(
alert: AlarmPresentation.Alert(
title: "Timer Complete",
stopButton: .stopButton,
secondaryButton: .repeatButton,
secondaryButtonBehavior: .countdown
),
countdown: AlarmPresentation.Countdown(
title: "Cooking Timer",
pauseButton: .pauseButton
),
paused: AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)
)let presentation = AlarmPresentation(
alert: AlarmPresentation.Alert(
title: "Timer Complete",
stopButton: .stopButton,
secondaryButton: .repeatButton,
secondaryButtonBehavior: .countdown
),
countdown: AlarmPresentation.Countdown(
title: "Cooking Timer",
pauseButton: .pauseButton
),
paused: AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)
)let alarms = try AlarmManager.shared.alarmslet alarms = try AlarmManager.shared.alarmstry await AlarmManager.shared.pause(id: alarmID)
try await AlarmManager.shared.resume(id: alarmID)try await AlarmManager.shared.pause(id: alarmID)
try await AlarmManager.shared.resume(id: alarmID)try await AlarmManager.shared.cancel(id: alarmID)try await AlarmManager.shared.cancel(id: alarmID)alarmUpdatesfor await alarms in AlarmManager.shared.alarmUpdates {
self.alarms = alarms
}alarmUpdatesfor await alarms in AlarmManager.shared.alarmUpdates {
self.alarms = alarms
}ActivityConfigurationAlarmAttributesstruct AlarmWidgetView: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AlarmAttributes<YourMetadata>.self) { context in
// Lock Screen presentation
VStack {
Text(context.attributes.presentation.alert.title)
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
.bold()
}
}
.padding()
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text(context.attributes.presentation.alert.title)
}
DynamicIslandExpandedRegion(.trailing) {
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
}
}
} compactLeading: {
Image(systemName: "alarm")
} compactTrailing: {
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
}
} minimal: {
Image(systemName: "alarm")
}
}
}
}ActivityConfigurationAlarmAttributesstruct AlarmWidgetView: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AlarmAttributes<YourMetadata>.self) { context in
// 锁屏界面展示
VStack {
Text(context.attributes.presentation.alert.title)
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
.bold()
}
}
.padding()
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text(context.attributes.presentation.alert.title)
}
DynamicIslandExpandedRegion(.trailing) {
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
}
}
} compactLeading: {
Image(systemName: "alarm")
} compactTrailing: {
if context.state.mode == .countdown {
Text(
timerInterval: context.state.countdownEndDate
.timeIntervalSinceNow,
countsDown: true
)
}
} minimal: {
Image(systemName: "alarm")
}
}
}
}import AlarmKit
@Observable
class AlarmViewModel {
var alarms: [Alarm] = []
private let manager = AlarmManager.shared
func requestAuthorization() {
Task {
_ = try? await manager.requestAuthorization()
}
}
func loadAndObserve() {
Task {
alarms = (try? manager.alarms) ?? []
for await updated in manager.alarmUpdates {
alarms = updated
}
}
}
func addAlarm(hour: Int, minute: Int, weekdays: Set<Locale.Weekday>) {
Task {
let time = Alarm.Schedule.Relative.Time(hour: hour, minute: minute)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: weekdays.isEmpty ? .never : .weekly(Array(weekdays))
))
let alert = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown
)
struct EmptyMetadata: AlarmMetadata {}
let config = AlarmManager.AlarmConfiguration(
countdownDuration: Alarm.CountdownDuration(
preAlert: nil, postAlert: 9 * 60
),
schedule: schedule,
attributes: AlarmAttributes(
presentation: AlarmPresentation(alert: alert),
metadata: EmptyMetadata(),
tintColor: .blue
),
sound: .default
)
_ = try? await manager.schedule(id: UUID(), configuration: config)
}
}
func cancel(id: UUID) {
Task { try? await manager.cancel(id: id) }
}
func togglePause(id: UUID, isPaused: Bool) {
Task {
if isPaused {
try? await manager.resume(id: id)
} else {
try? await manager.pause(id: id)
}
}
}
}import AlarmKit
@Observable
class AlarmViewModel {
var alarms: [Alarm] = []
private let manager = AlarmManager.shared
func requestAuthorization() {
Task {
_ = try? await manager.requestAuthorization()
}
}
func loadAndObserve() {
Task {
alarms = (try? manager.alarms) ?? []
for await updated in manager.alarmUpdates {
alarms = updated
}
}
}
func addAlarm(hour: Int, minute: Int, weekdays: Set<Locale.Weekday>) {
Task {
let time = Alarm.Schedule.Relative.Time(hour: hour, minute: minute)
let schedule = Alarm.Schedule.relative(.init(
time: time,
repeats: weekdays.isEmpty ? .never : .weekly(Array(weekdays))
))
let alert = AlarmPresentation.Alert(
title: "Alarm",
stopButton: .stopButton,
secondaryButton: .snoozeButton,
secondaryButtonBehavior: .countdown
)
struct EmptyMetadata: AlarmMetadata {}
let config = AlarmManager.AlarmConfiguration(
countdownDuration: Alarm.CountdownDuration(
preAlert: nil, postAlert: 9 * 60
),
schedule: schedule,
attributes: AlarmAttributes(
presentation: AlarmPresentation(alert: alert),
metadata: EmptyMetadata(),
tintColor: .blue
),
sound: .default
)
_ = try? await manager.schedule(id: UUID(), configuration: config)
}
}
func cancel(id: UUID) {
Task { try? await manager.cancel(id: id) }
}
func togglePause(id: UUID, isPaused: Bool) {
Task {
if isPaused {
try? await manager.resume(id: id)
} else {
try? await manager.pause(id: id)
}
}
}
}struct AlarmListView: View {
@State private var viewModel = AlarmViewModel()
var body: some View {
NavigationStack {
List(viewModel.alarms, id: \.id) { alarm in
AlarmRow(alarm: alarm, viewModel: viewModel)
}
.navigationTitle("Alarms")
.onAppear {
viewModel.requestAuthorization()
viewModel.loadAndObserve()
}
}
}
}struct AlarmListView: View {
@State private var viewModel = AlarmViewModel()
var body: some View {
NavigationStack {
List(viewModel.alarms, id: \.id) { alarm in
AlarmRow(alarm: alarm, viewModel: viewModel)
}
.navigationTitle("Alarms")
.onAppear {
viewModel.requestAuthorization()
viewModel.loadAndObserve()
}
}
}
}| Practice | Detail |
|---|---|
| Request authorization early | On first launch or first alarm creation attempt |
| Handle denial gracefully | Guide users to Settings if permission was denied |
| Persist alarm UUIDs | Store IDs to manage alarms across app launches |
| Implement widget extension | Required for countdown/Dynamic Island presentation |
Use | Keep UI in sync; don't poll or cache stale state |
| Test on physical device | Alarm sounds, notifications, and Live Activities require real hardware |
| Respect system limits | There is a system-imposed cap on alarms per app |
Use | Not |
| 实践建议 | 详细说明 |
|---|---|
| 尽早请求授权 | 在首次启动或首次尝试创建闹钟时请求 |
| 优雅处理权限拒绝 | 如果权限被拒绝,引导用户前往设置界面开启 |
| 持久化闹钟UUID | 存储ID以在应用重启后管理闹钟 |
| 实现小组件扩展 | 倒计时/Dynamic Island展示的必要条件 |
使用 | 保持UI同步;不要轮询或缓存过期状态 |
| 在物理设备上测试 | 闹钟声音、通知和Live Activities需要真实硬件支持 |
| 遵守系统限制 | 系统对每个应用可设置的闹钟数量有上限 |
使用 | 正确的属性名称是 |