axiom-alarmkit-ref
AlarmKit Reference
Complete API reference for AlarmKit, Apple's framework for scheduling alarms and countdown timers with system-level alerting, Dynamic Island integration, and focus/silent mode override.
Overview
AlarmKit lets apps create alarms and timers that behave like the built-in Clock app -- they override Do Not Disturb, appear in the Dynamic Island, and show on the Lock Screen. The framework handles scheduling, snooze, pause/resume, and UI presentation through a small set of types centered on AlarmManager.
System Requirements
- iOS 26+ (AlarmKit introduced in iOS 26)
- Widget Extension required for Live Activity / Dynamic Island presentation
- Physical device recommended for alarm sound and notification testing
Part 1: Key Components
AlarmManager
Singleton entry point for all alarm operations.
import AlarmKit
let manager = AlarmManager.shared
All scheduling, cancellation, and observation flows through this shared instance.
Alarm
Describes an alarm that can alert once or on a repeating schedule.
struct Alarm {
var id: UUID
var schedule: Schedule?
var countdownDuration: CountdownDuration?
var state: AlarmState
}
AlarmPresentation
Content for the alarm UI across three states -- alerting, counting down, and paused.
struct AlarmPresentation {
var alert: Alert // Required: shown when alarm fires
var countdown: Countdown? // Optional: shown during countdown
var paused: Paused? // Optional: shown when paused
}
AlarmAttributes
Generic container pairing presentation with app-specific metadata and tint color. Used to configure the Live Activity widget.
struct AlarmAttributes<Metadata: AlarmMetadata> {
var presentation: AlarmPresentation
var metadata: Metadata
var tintColor: Color
}
AlarmMetadata
Protocol for app-specific data attached to an alarm. Conform an empty struct for minimal usage, or add properties for richer UI.
struct RecipeMetadata: AlarmMetadata {
let recipeName: String
let cookingStep: String
}
Part 2: Authorization
Apps must request permission before scheduling alarms. Add NSAlarmKitUsageDescription to Info.plist.
Requesting Authorization
func requestAlarmAuthorization() async -> Bool {
do {
let state = try await AlarmManager.shared.requestAuthorization()
return state == .authorized
} catch {
print("Authorization error: \(error)")
return false
}
}
Checking Current State
Use authorizationState (not authorizationStatus) to read the current value:
let state = await AlarmManager.shared.authorizationState
// .authorized | .denied | .notDetermined
Observing Authorization Changes
for await authState in AlarmManager.shared.authorizationUpdates {
switch authState {
case .authorized: enableAlarmUI()
case .denied: showPermissionPrompt()
case .notDetermined: break
@unknown default: break
}
}
Part 3: Scheduling Alarms
Every alarm requires a UUID, an AlarmManager.AlarmConfiguration, and a call to schedule(id:configuration:).
One-Time Alarm
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)
Repeating Alarm
Use .weekly(Array(weekdays)) for specific days:
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])
))
Countdown Timer
Set schedule: nil and provide countdownDuration with a preAlert interval:
let 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
)
Timers support pause/resume and show a countdown presentation when AlarmPresentation.countdown is provided.
Snooze Configuration
Snooze uses CountdownDuration.postAlert combined with a .snoozeButton secondary action:
let 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
)
Part 4: Customizing Alarm UI
Alert Presentation
The alert state is shown when the alarm fires. The stop button is required; secondary button is optional.
// 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
)
Countdown Presentation
Shown while a timer counts down. Only relevant for alarms with countdownDuration.preAlert.
let countdown = AlarmPresentation.Countdown(
title: "Timer Running",
pauseButton: .pauseButton
)
Paused Presentation
Shown when a countdown timer is paused.
let paused = AlarmPresentation.Paused(
title: "Timer Paused",
resumeButton: .resumeButton
)
Full Three-State Presentation
Combine all three for a complete timer experience:
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
)
)
Part 5: Managing Alarms
Retrieve All Alarms
let alarms = try AlarmManager.shared.alarms
Pause / Resume
try await AlarmManager.shared.pause(id: alarmID)
try await AlarmManager.shared.resume(id: alarmID)
Cancel
try await AlarmManager.shared.cancel(id: alarmID)
Observe Alarm Updates
Use alarmUpdates to keep UI in sync. An alarm absent from the emitted array is no longer scheduled.
for await alarms in AlarmManager.shared.alarmUpdates {
self.alarms = alarms
}
Part 6: Live Activity Integration
AlarmKit alarms appear in the Dynamic Island and Lock Screen through ActivityConfiguration. Add a Widget Extension target and implement the widget using AlarmAttributes.
struct 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")
}
}
}
}
Part 7: SwiftUI Integration
ViewModel Pattern with @Observable
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)
}
}
}
}
Alarm List View
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()
}
}
}
}
Part 8: Best Practices
| 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 alarmUpdates |
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 authorizationState |
Not authorizationStatus -- the correct property name is authorizationState |
Resources
WWDC: 2025-230
Docs: /alarmkit, /alarmkit/alarmmanager, /alarmkit/alarm, /alarmkit/alarmpresentation, /alarmkit/alarmattributes
Skills: axiom-extensions-widgets-ref, axiom-swiftui-26-ref