← All articles
4 min readManav

Scheduling Alarms in iOS Apps with AlarmKit: A Complete Guide

The introduction of AlarmKit in iOS has revolutionized how developers can integrate alarm functionality into their applications. This powerful framework allows apps to schedule system-level alarms…

swiftuiswiftios

The introduction of AlarmKit in iOS has revolutionized how developers can integrate alarm functionality into their applications. This powerful framework allows apps to schedule system-level alarms that persist even when the app isn’t running, providing users with reliable wake-up calls and reminders directly from third-party applications.

#Understanding AlarmKit

AlarmKit is Apple’s framework that enables apps to create and manage alarms through the system’s alarm infrastructure. Unlike local notifications, AlarmKit alarms integrate seamlessly with the native Clock app and provide a more robust scheduling system that users can trust for critical wake-up times.

The framework offers several key advantages over traditional notification-based approaches. Alarms created with AlarmKit appear in the system’s Clock app, allowing users to manage them alongside their regular alarms. These alarms also respect the device’s Do Not Disturb settings appropriately and provide a consistent user experience across the system.

#Key Components

AlarmKit consists of several essential components that work together to provide comprehensive alarm functionality:

AlarmManager serves as the central coordinator for all alarm operations, handling authorization, scheduling, and state management. AlarmConfiguration defines the behavior and presentation of alarms, including countdown durations, schedules, and visual attributes. AlarmPresentation controls how alarms appear to users, with separate configurations for alert, countdown, and paused states.

#Basic Setup and Authorization

import AlarmKit
import SwiftUI

@Observable class ViewModel {
    @ObservationIgnored private let alarmManager = AlarmManager.shared
    @MainActor var alarmsMap = [UUID: (Alarm, LocalizedStringResource)]()

    private func requestAuthorization() async -> Bool {
        switch alarmManager.authorizationState {
        case .notDetermined:
            do {
                let state = try await alarmManager.requestAuthorization()
                return state == .authorized
            } catch {
                print("Error occurred while requesting authorization: \(error)")
                return false
            }
        case .denied: return false
        case .authorized: return true
        @unknown default: return false
        }
    }
}

#Scheduling Different Types of Alarms

AlarmKit supports various alarm configurations to meet different use cases. Here are the main patterns:

Alert-Only Alarms provide simple notification functionality:

func scheduleAlertOnlyExample() {
    let alertContent = AlarmPresentation.Alert(title: "Wake Up", stopButton: .stopButton)

    let attributes = AlarmAttributes(presentation: AlarmPresentation(alert: alertContent),
                                                  tintColor: Color.accentColor)

    let alarmConfiguration = AlarmConfiguration(schedule: .twoMinsFromNow, attributes: attributes)

    scheduleAlarm(id: UUID(), label: "Wake Up", alarmConfiguration: alarmConfiguration)
}

Countdown Alarms include pre-alert timing and repeat functionality:

func scheduleCountdownAlertExample() {
    let alertContent = AlarmPresentation.Alert(title: "Food Ready",
                                               stopButton: .stopButton,
                                               secondaryButton: .repeatButton,
                                               secondaryButtonBehavior: .countdown)

    let countdownContent = AlarmPresentation.Countdown(title: "Cooking", pauseButton: .pauseButton)
    let pausedContent = AlarmPresentation.Paused(title: "Paused", resumeButton: .resumeButton)

    let attributes = AlarmAttributes(presentation: AlarmPresentation(alert: alertContent,
                                                                    countdown: countdownContent,
                                                                    paused: pausedContent),
                                     metadata: CookingData(method: .oven),
                                     tintColor: Color.accentColor)

    let alarmConfiguration = AlarmConfiguration(countdownDuration: .init(preAlert: 15 * 60, postAlert: 15 * 60),
                                                attributes: attributes)

    scheduleAlarm(id: UUID(), label: "Food is cooking", alarmConfiguration: alarmConfiguration)
}

Custom Button Alarms can launch your app directly:

func scheduleCustomButtonAlertExample() {
    let alertContent = AlarmPresentation.Alert(title: "Wake Up",
                                               stopButton: .stopButton,
                                               secondaryButton: .openAppButton,
                                               secondaryButtonBehavior: .custom)

    let attributes = AlarmAttributes(presentation: AlarmPresentation(alert: alertContent),
                                                  tintColor: Color.accentColor)

    let alarmConfiguration = AlarmConfiguration(schedule: .twoMinsFromNow,
                                                attributes: attributes,
                                                secondaryIntent: OpenAlarmAppIntent(alarmID: id.uuidString))

    scheduleAlarm(id: id, label: "Wake Up", alarmConfiguration: alarmConfiguration)
}

#Managing Alarm State

AlarmKit provides real-time updates about alarm states through an async sequence:

private func observeAlarms() {
    Task {
        for await incomingAlarms in alarmManager.alarmUpdates {
            updateAlarmState(with: incomingAlarms)
        }
    }
}

private func updateAlarmState(with remoteAlarms: [Alarm]) {
    Task { @MainActor in
        remoteAlarms.forEach { updated in
            alarmsMap[updated.id, default: (updated, "Alarm (Old Session)")].0 = updated
        }

        let knownAlarmIDs = Set(alarmsMap.keys)
        let incomingAlarmIDs = Set(remoteAlarms.map(\.id))

        let removedAlarmIDs = Set(knownAlarmIDs.subtracting(incomingAlarmIDs))
        removedAlarmIDs.forEach {
            alarmsMap[$0] = nil
        }
    }
}

#SwiftUI Integration

The framework integrates seamlessly with SwiftUI through the Observable pattern:

struct ContentView: View {
    @State private var viewModel = ViewModel()
    @State private var showAddSheet = false

    var body: some View {
        NavigationStack {
            if viewModel.hasUpcomingAlerts {
                alarmList(alarms: Array(viewModel.alarmsMap.values))
            } else {
                ContentUnavailableView("No Alarms",
                                     systemImage: "clock.badge.exclamationmark",
                                     description: Text("Add a new alarm by tapping + button."))
            }
        }
        .environment(viewModel)
        .onAppear {
            viewModel.fetchAlarms()
        }
    }
}

#Advanced Features

AlarmKit supports sophisticated scheduling options including fixed dates and relative times. The framework also allows custom metadata to be associated with alarms, enabling rich contextual information that persists across app launches.

Button customisation is extensive, with support for custom text, colors, and system images. Secondary intents can trigger app-specific actions when users interact with alarm notifications.

#Best Practices

Always request authorization before attempting to schedule alarms, and handle the various authorization states appropriately. Use meaningful alarm labels and configure appropriate tint colors to maintain visual consistency with your app’s design.

Implement proper error handling for scheduling failures, and consider edge cases like reaching system limits for the number of scheduled alarms. The framework automatically manages alarm persistence, but your app should still maintain its own state for UI consistency.

#Conclusion

AlarmKit represents a significant advancement in iOS alarm capabilities, providing developers with powerful tools to create reliable, system-integrated timing solutions. By following the patterns demonstrated in Apple’s sample code and implementing proper state management, developers can create alarm experiences that users trust for their most critical timing needs.

The framework’s integration with the native Clock app ensures consistent user experience while still allowing for rich customization and app-specific functionality. Whether building productivity apps, cooking timers, or fitness reminders, AlarmKit provides the foundation for reliable alarm systems that work seamlessly within the iOS ecosystem.

Found this useful? Share it.

Keep reading