Loading...
Loading...
Implement alarm and countdown timer features using Apple's AlarmKit framework (iOS 26+ / iPadOS 26+). Covers AlarmManager for scheduling alarms and timers, AlarmAttributes and AlarmPresentation for Lock Screen and Dynamic Island UI, AlarmButton for stop/snooze actions, authorization flows, alarm state observation, and Live Activity integration. Use when building wake-up alarms, countdown timers with system UI, or alarm-style notifications that surface on the Lock Screen and Dynamic Island.
npx skill4agent add dpearson2699/swift-ios-skills alarmkitAlarmAttributesAlarmPresentationreferences/alarmkit-patterns.mdimport AlarmKitNSAlarmKitUsageDescriptionAlarmManager.shared.requestAuthorization()AlarmPresentationAlarmAttributesAlarmManager.AlarmConfigurationAlarmManager.shared.schedule(id:configuration:)alarmManager.alarmUpdateslet 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 ( | 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 |
.alarm(schedule:...).timer(duration:...)Alarm.Schedule// 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 entirelycancel(id:)pause(id:)resume(id:)stop(id:)countdown(id:)Alarm.CountdownDuration// 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.CountdownDurationpreAlertpostAlertlet 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
)Alarmstate| State | Meaning |
|---|---|
| Waiting to fire (alarm mode) or waiting to start countdown |
| Actively counting down (timer or pre-alert phase) |
| Countdown paused by user or app |
| 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
}
}
}alarmUpdatesAlarmAttributesActivityAttributesMetadataAlarmMetadata// 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
)AlarmPresentationStateContentStateMode.alert(Alert).countdown(Countdown).paused(Paused)AlarmPresentationState.modeAlarmButtonlet stopButton = AlarmButton(
text: "Stop",
textColor: .red,
systemImageName: "stop.fill"
)
let snoozeButton = AlarmButton(
text: "Snooze",
textColor: .white,
systemImageName: "bell.slash"
)| Behavior | Effect |
|---|---|
| Restarts a countdown using |
| Triggers the |
AlarmAttributesAlarmPresentationStatestruct 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")
}
}
}
}NSAlarmKitUsageDescriptionrequestAuthorization().denied.timer.alarm.weekly([...])alarmUpdatesalarmManager.alarmUpdatesstopIntentLiveActivityIntentAlarmMetadatastopButtonAlarmPresentation.Alertinit(title:secondaryButton:secondaryButtonBehavior:)NSAlarmKitUsageDescription.deniedAlarmPresentationAlarmAttributesAlarmMetadataalarmUpdatesstopIntentsecondaryIntentLiveActivityIntentpostAlertCountdownDuration.countdownAlarmAttributesAlarmManager.AlarmError.maximumLimitReachedreferences/alarmkit-patterns.md