All skills

SwiftUI framework skills — data flow (identity, Observation, state ownership), layout & containers (Layout protocol, lazy-stack performance), AlarmKit, 3D charts, rich text editing, customizable toolbars, and WebKit embedding. Use for SwiftUI state/rendering bugs, custom layouts, scroll performance, or these feature areas.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/swiftui

This session only. Nothing lands on disk.

alarmkitscheduling.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Scheduling Alarms and Timers

Authorization setup, creating one-time/repeating alarms and countdown timers, managing alarm lifecycle, and observing state changes.

Authorization

Info.plist Requirement

You must add NSAlarmKitUsageDescription to your app's Info.plist before requesting authorization. Without it, the authorization request will fail silently.

<key>NSAlarmKitUsageDescription</key>
<string>This app needs alarm access to wake you up at your scheduled times.</string>

Requesting Authorization

import AlarmKit

func requestAlarmAccess() async {
    let state = await AlarmManager.shared.requestAuthorization()

    switch state {
    case .authorized:
        // Can schedule alarms
        break
    case .denied:
        // Show settings prompt to the user
        break
    case .notDetermined:
        // Should not happen after request, but handle gracefully
        break
    @unknown default:
        break
    }
}

Checking Current Authorization

// ✅ Correct property name
let state = AlarmManager.shared.authorizationState

// ❌ WRONG: This property does NOT exist
let state = AlarmManager.shared.authorizationStatus

Observing Authorization Changes

func observeAuthorizationChanges() async {
    for await state in AlarmManager.shared.authorizationUpdates {
        switch state {
        case .authorized:
            // Update UI to show alarm features
            break
        case .denied:
            // Hide alarm features, show explanation
            break
        default:
            break
        }
    }
}

Creating Alarms

All alarms are created through AlarmManager.shared.schedule(id:configuration:).

One-Time Alarm

A single alarm that fires once and does not repeat.

import AlarmKit

func scheduleOneTimeAlarm() async throws -> Alarm {
    let alarmID = UUID()

    let alarm = try await AlarmManager.shared.schedule(id: alarmID) {
        Alarm.Schedule.relative(
            .repeats: .never
        )

        AlarmPresentation.Alert(
            title: "Wake Up!",
            stopButton: AlarmButton(label: "Stop"),
            secondaryButton: .snoozeButton()
        )
    }

    // Persist the alarm ID for later management
    UserDefaults.standard.set(alarmID.uuidString, forKey: "morningAlarmID")

    return alarm
}

Repeating Alarm

An alarm that fires on specific days of the week.

func scheduleWeekdayAlarm() async throws -> Alarm {
    let alarmID = UUID()

    let alarm = try await AlarmManager.shared.schedule(id: alarmID) {
        Alarm.Schedule.relative(
            .repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday])
        )

        AlarmPresentation.Alert(
            title: "Time for Work",
            stopButton: AlarmButton(label: "Dismiss"),
            secondaryButton: .snoozeButton()
        )
    }

    return alarm
}

Countdown Timer

Timers use CountdownDuration and do not include a schedule. The preAlert parameter is the countdown duration in seconds before the alert fires. The postAlert parameter defines an optional snooze duration.

// ✅ Timer with countdown -- no schedule
func startTimer(minutes: Int) async throws -> Alarm {
    let alarmID = UUID()

    let alarm = try await AlarmManager.shared.schedule(id: alarmID) {
        CountdownDuration(
            preAlert: TimeInterval(minutes * 60),
            postAlert: 60  // 60-second snooze after alert
        )

        AlarmPresentation.Countdown(
            title: "Timer",
            pauseButton: AlarmButton(label: "Pause")
        )

        AlarmPresentation.Alert(
            title: "Time's Up!",
            stopButton: AlarmButton(label: "Done"),
            secondaryButton: .repeatButton()
        )

        AlarmPresentation.Paused(
            title: "Timer Paused",
            resumeButton: AlarmButton(label: "Resume")
        )
    }

    return alarm
}
// ❌ WRONG: Do not pass a schedule with a timer
func wrongTimer() async throws -> Alarm {
    let alarmID = UUID()
    return try await AlarmManager.shared.schedule(id: alarmID) {
        Alarm.Schedule.relative(.repeats: .never)  // Wrong -- timers don't use schedules
        CountdownDuration(preAlert: 300, postAlert: 60)
        // ...
    }
}

Managing Alarms

Listing Active Alarms

let activeAlarms = AlarmManager.shared.alarms
for alarm in activeAlarms {
    print("Alarm \(alarm.id)")
}

Pause, Resume, and Cancel

let alarmID: UUID = // ... retrieved from persistence

// Pause a countdown timer
try await AlarmManager.shared.pause(id: alarmID)

// Resume a paused timer
try await AlarmManager.shared.resume(id: alarmID)

// Cancel an alarm entirely
try await AlarmManager.shared.cancel(id: alarmID)

Observing Alarm Updates

Use the alarmUpdates async sequence to keep your app's state synchronized with the system. This is the recommended way to track alarm state changes rather than polling.

func observeAlarms() async {
    for await alarms in AlarmManager.shared.alarmUpdates {
        // alarms is the updated [Alarm] array
        await MainActor.run {
            self.activeAlarms = alarms
        }
    }
}

Persisting Alarm IDs

AlarmKit does not provide a way to look up alarms by custom identifiers. You must store the UUID returned from scheduling so you can pause, resume, or cancel alarms later.

// ✅ Persist alarm IDs
struct AlarmStore {
    private let defaults = UserDefaults.standard
    private let key = "scheduledAlarmIDs"

    func save(alarmID: UUID, label: String) {
        var stored = defaults.dictionary(forKey: key) as? [String: String] ?? [:]
        stored[alarmID.uuidString] = label
        defaults.set(stored, forKey: key)
    }

    func alarmID(for label: String) -> UUID? {
        let stored = defaults.dictionary(forKey: key) as? [String: String] ?? [:]
        return stored.first(where: { $0.value == label }).map { UUID(uuidString: $0.key)! }
    }

    func remove(alarmID: UUID) {
        var stored = defaults.dictionary(forKey: key) as? [String: String] ?? [:]
        stored.removeValue(forKey: alarmID.uuidString)
        defaults.set(stored, forKey: key)
    }
}

Error Handling

Always wrap scheduling and management calls in do/catch blocks. The system imposes a limit on the number of active alarms, and calls can fail for various reasons (authorization denied, system limit reached, invalid configuration).

do {
    let alarm = try await AlarmManager.shared.schedule(id: UUID()) {
        // ... configuration
    }
} catch {
    // Handle error -- could be authorization, system limit, or invalid config
    print("Failed to schedule alarm: \(error.localizedDescription)")
}

Common Patterns

Alarm Manager View Model

import AlarmKit
import SwiftUI

@Observable
final class AlarmViewModel {
    private(set) var alarms: [Alarm] = []
    private(set) var isAuthorized = false

    func setup() async {
        let state = await AlarmManager.shared.requestAuthorization()
        isAuthorized = (state == .authorized)

        // Start observing in a detached task
        Task {
            await observeAlarms()
        }
    }

    private func observeAlarms() async {
        for await updatedAlarms in AlarmManager.shared.alarmUpdates {
            await MainActor.run {
                self.alarms = updatedAlarms
            }
        }
    }

    func cancelAlarm(id: UUID) async {
        do {
            try await AlarmManager.shared.cancel(id: id)
        } catch {
            print("Cancel failed: \(error)")
        }
    }
}

Source: SKILL.md on GitHub

1 warning2mo3 checks · Risk SAFE
  • Gen Agent Trust Hub2mo

    The skill provides comprehensive documentation and code examples for SwiftUI development, including data flow, layout, AlarmKit, 3D charts, rich text, and WebKit integration. No security issues or malicious patterns were detected.

  • Socket2mo

    No alerts

  • Snyk2mo

    Risk: MEDIUM · 1 issue

Signed by skilld at 32566aa. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Reads files
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 3 allowed tools
ReadGlobGrep

README badge

README badge for rshankras/claude-code-apple-skills/swiftui