All skills

Xcode 26 and Apple platform update notes with a focus on Liquid Glass, Foundation Models Framework, and Apple Intelligence. Use when asked to explain, summarize, compare, or implement Xcode 26 era framework changes across SwiftUI, UIKit, AppKit, Foundation, App Intents, StoreKit, MapKit, WidgetKit, visionOS, or Swift language features.

Use this Skill: https://skilld.dev/gh/harryworld/xcode26-agent-skills/xcode-26

This session only. Nothing lands on disk.

referencesSwiftUI-AlarmKit-Integration.md

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

Using AlarmKit in a SwiftUI App

Overview

AlarmKit is a framework introduced in iOS 26 that allows developers to create custom alarms and timers within their apps. It provides a comprehensive system for managing alarms with customizable schedules and UI. AlarmKit supports one-time and repeating alarms, with options for countdown durations and snooze functionality. It handles alarm authorization and provides UI for both templated and widget presentations.

Key features:

  • Schedule one-time or repeating alarms
  • Create countdown timers
  • Customize alarm UI and presentation
  • Integrate with Live Activities for Dynamic Island and Lock Screen
  • Override device focus and silent mode

Key Components

AlarmManager

The central class for interacting with the alarm system:

// Access the shared instance
let alarmManager = AlarmManager.shared

Alarm

A structure that describes an alarm that can alert once or on a repeating schedule:

struct Alarm {
    var id: UUID
    var schedule: Schedule?
    var countdownDuration: CountdownDuration?
    // Other properties
}

AlarmPresentation

Defines the content required for the alarm UI in different states:

struct AlarmPresentation {
    var alert: Alert
    var countdown: Countdown?
    var paused: Paused?
}

AlarmAttributes

Contains all information necessary for the alarm UI:

struct AlarmAttributes<Metadata: AlarmMetadata> {
    var presentation: AlarmPresentation
    var metadata: Metadata
    var tintColor: Color
}

Authorization

Before scheduling alarms, your app needs to request authorization:

// Add to Info.plist
// NSAlarmKitUsageDescription: "We'll schedule alerts for alarms you create within our app."

func requestAlarmAuthorization() async -> Bool {
    do {
        let state = try await AlarmManager.shared.requestAuthorization()
        return state == .authorized
    } catch {
        print("Error occurred while requesting authorization: \(error)")
        return false
    }
}

You can also check the current authorization status:

func checkAuthorizationStatus() async {
    let status = await AlarmManager.shared.authorizationState
    switch status {
    case .authorized:
        print("Authorized to schedule alarms")
    case .denied:
        print("Permission denied")
    case .notDetermined:
        print("Permission not yet requested")
    @unknown default:
        print("Unknown status")
    }
}

NOTE: Use authorizationState — never use authorizationStatus when checking the authorization status.

Creating Alarms

One-time Alarm

func createOneTimeAlarm(hour: Int, minute: Int) async throws -> Alarm {
    // Create a unique ID
    let id = UUID()
    
    // Create the schedule for a specific time
    let time = Alarm.Schedule.Relative.Time(hour: hour, minute: minute)
    let schedule = Alarm.Schedule.relative(.init(
        time: time,
        repeats: .never
    ))
    
    // Create the alarm presentation
    let alertContent = AlarmPresentation.Alert(
        title: "Wake Up",
        stopButton: .stopButton,
        secondaryButton: .openAppButton,
        secondaryButtonBehavior: .custom
    )
    
    let presentation = AlarmPresentation(alert: alertContent)
    
    // Create attributes with presentation and metadata
    struct EmptyMetadata: AlarmMetadata {}
    let attributes = AlarmAttributes(
        presentation: presentation,
        metadata: EmptyMetadata(),
        tintColor: .blue
    )
    
    // Create the configuration
    let configuration = AlarmManager.AlarmConfiguration(
        countdownDuration: nil,
        schedule: schedule,
        attributes: attributes,
        sound: .default
    )
    
    // Schedule the alarm
    return try await AlarmManager.shared.schedule(id: id, configuration: configuration)
}

Repeating Alarm

func createWeeklyAlarm(hour: Int, minute: Int, weekdays: Set<Locale.Weekday>) async throws -> Alarm {
    let id = UUID()
    
    // Create the schedule for repeating on specific days
    let time = Alarm.Schedule.Relative.Time(hour: hour, minute: minute)
    let schedule = Alarm.Schedule.relative(.init(
        time: time,
        repeats: .weekly(Array(weekdays))
    ))
    
    // Create the alarm presentation
    let alertContent = AlarmPresentation.Alert(
        title: "Weekly Reminder",
        stopButton: .stopButton,
        secondaryButton: .snoozeButton,
        secondaryButtonBehavior: .countdown
    )
    
    // Create countdown duration for snooze (9 minutes)
    let countdownDuration = Alarm.CountdownDuration(
        preAlert: nil,
        postAlert: 9 * 60
    )
    
    let presentation = AlarmPresentation(alert: alertContent)
    
    // Create attributes with presentation and metadata
    struct EmptyMetadata: AlarmMetadata {}
    let attributes = AlarmAttributes(
        presentation: presentation,
        metadata: EmptyMetadata(),
        tintColor: .green
    )
    
    // Create the configuration
    let configuration = AlarmManager.AlarmConfiguration(
        countdownDuration: countdownDuration,
        schedule: schedule,
        attributes: attributes,
        sound: .default
    )
    
    // Schedule the alarm
    return try await AlarmManager.shared.schedule(id: id, configuration: configuration)
}

Timer (Countdown)

func createCountdownTimer(seconds: TimeInterval) async throws -> Alarm {
    let id = UUID()
    
    // Create countdown duration
    let countdownDuration = Alarm.CountdownDuration(
        preAlert: seconds,
        postAlert: 10 // Optional post-alert countdown for snooze
    )
    
    // Create the alarm presentation with all states
    let alertContent = AlarmPresentation.Alert(
        title: "Timer Complete",
        stopButton: .stopButton,
        secondaryButton: .repeatButton,
        secondaryButtonBehavior: .countdown
    )
    
    let countdownContent = AlarmPresentation.Countdown(
        title: "Timer Running",
        pauseButton: .pauseButton
    )
    
    let pausedContent = AlarmPresentation.Paused(
        title: "Timer Paused",
        resumeButton: .resumeButton
    )
    
    let presentation = AlarmPresentation(
        alert: alertContent,
        countdown: countdownContent,
        paused: pausedContent
    )
    
    // Create attributes with presentation and metadata
    struct TimerMetadata: AlarmMetadata {
        let purpose: String
    }
    
    let attributes = AlarmAttributes(
        presentation: presentation,
        metadata: TimerMetadata(purpose: "Cooking Timer"),
        tintColor: .orange
    )
    
    // Create the configuration
    let configuration = AlarmManager.AlarmConfiguration(
        countdownDuration: countdownDuration,
        schedule: nil, // No schedule for a timer
        attributes: attributes,
        sound: .default
    )
    
    // Schedule the alarm
    return try await AlarmManager.shared.schedule(id: id, configuration: configuration)
}

Customizing Alarm UI

Alert Presentation

// Basic alert with stop button only
let basicAlert = AlarmPresentation.Alert(
    title: "Alarm",
    stopButton: .stopButton
)

// Alert with stop and snooze buttons
let alertWithSnooze = AlarmPresentation.Alert(
    title: "Wake Up",
    stopButton: .stopButton,
    secondaryButton: .snoozeButton,
    secondaryButtonBehavior: .countdown
)

// Alert with custom button labels
let customAlert = AlarmPresentation.Alert(
    title: "Medication Reminder",
    stopButton: AlarmButton(label: "Taken"),
    secondaryButton: AlarmButton(label: "Remind Later"),
    secondaryButtonBehavior: .countdown
)

Countdown Presentation

// Basic countdown
let countdown = AlarmPresentation.Countdown(
    title: "Timer",
    pauseButton: .pauseButton
)

// Countdown with custom button label
let customCountdown = AlarmPresentation.Countdown(
    title: "Cooking Timer",
    pauseButton: .pauseButton
)

Paused Presentation

// Basic paused state
let paused = AlarmPresentation.Paused(
    title: "Paused",
    resumeButton: .resumeButton
)

// Paused with custom button label
let customPaused = AlarmPresentation.Paused(
    title: "Timer Paused",
    resumeButton: .resumeButton
)

Custom Metadata

// Define custom metadata for your alarm
struct RecipeMetadata: AlarmMetadata {
    let recipeName: String
    let cookingStep: String
}

// Use the metadata in your alarm attributes
let recipeAttributes = AlarmAttributes(
    presentation: presentation,
    metadata: RecipeMetadata(
        recipeName: "Chocolate Cake",
        cookingStep: "Remove from oven"
    ),
    tintColor: .brown
)

Managing Alarms

Scheduling an Alarm

func scheduleAlarm(id: UUID, configuration: AlarmManager.AlarmConfiguration) async throws -> Alarm {
    return try await AlarmManager.shared.schedule(id: id, configuration: configuration)
}

Retrieving All Alarms

func getAllAlarms() throws -> [Alarm] {
    return try AlarmManager.shared.alarms
}

Pausing an Alarm

func pauseAlarm(id: UUID) async throws {
    try await AlarmManager.shared.pause(id: id)
}

Resuming an Alarm

func resumeAlarm(id: UUID) async throws {
    try await AlarmManager.shared.resume(id: id)
}

Canceling an Alarm

func cancelAlarm(id: UUID) async throws {
    try await AlarmManager.shared.cancel(id: id)
}

Observing Alarm Changes

Use the alarmUpdates property to observe changes to alarms:

func observeAlarmChanges() {
    Task {
        for await alarms in AlarmManager.shared.alarmUpdates {
            // Update your UI with the latest alarms
            updateAlarmState(with: alarms)
        }
    }
}

func updateAlarmState(with alarms: [Alarm]) {
    // Update your app's state with the latest alarms
    // An alarm not included in this array is no longer scheduled
}

You can also observe authorization state changes:

func observeAuthorizationChanges() {
    Task {
        for await authState in AlarmManager.shared.authorizationUpdates {
            switch authState {
            case .authorized:
                // User granted permission
            case .denied:
                // User denied permission
            case .notDetermined:
                // Permission not yet requested
            @unknown default:
                break
            }
        }
    }
}

Live Activities Integration

AlarmKit integrates with Live Activities to show countdown timers in the Dynamic Island and Lock Screen. You need to create a widget extension for this:

  1. Add a Widget Extension target to your project
  2. Implement the widget to display the alarm state:
struct AlarmWidgetView: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: AlarmAttributes<YourMetadata>.self) { context in
            // Dynamic Island and Lock Screen UI
            VStack {
                Text(context.state.mode == .countdown ? "Counting down" : "Alarm set")
                if context.state.mode == .countdown {
                    Text(timerInterval: context.state.countdownEndDate.timeIntervalSinceNow, countsDown: true)
                        .bold()
                }
            }
            .padding()
        } dynamicIsland: { context in
            // Dynamic Island UI
            DynamicIsland {
                // Expanded UI
                DynamicIslandExpandedRegion(.leading) {
                    Text(context.attributes.presentation.alert.title)
                }
                DynamicIslandExpandedRegion(.trailing) {
                    if context.state.mode == .countdown {
                        Text(timerInterval: context.state.countdownEndDate.timeIntervalSinceNow, countsDown: true)
                    }
                }
            } compactLeading: {
                // Compact leading UI
                Image(systemName: "alarm")
            } compactTrailing: {
                // Compact trailing UI
                if context.state.mode == .countdown {
                    Text(timerInterval: context.state.countdownEndDate.timeIntervalSinceNow, countsDown: true)
                }
            } minimal: {
                // Minimal UI
                Image(systemName: "alarm")
            }
        }
    }
}

SwiftUI Integration Example

Here's a complete example of a simple alarm app using AlarmKit:

import SwiftUI
import AlarmKit

struct AlarmApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @StateObject private var viewModel = AlarmViewModel()
    @State private var showingAddAlarm = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.alarms, id: \.id) { alarm in
                    AlarmRow(alarm: alarm, viewModel: viewModel)
                }
                .onDelete { indexSet in
                    viewModel.deleteAlarms(at: indexSet)
                }
            }
            .navigationTitle("Alarms")
            .toolbar {
                Button(action: {
                    showingAddAlarm = true
                }) {
                    Image(systemName: "plus")
                }
            }
            .sheet(isPresented: $showingAddAlarm) {
                AddAlarmView(viewModel: viewModel)
            }
            .onAppear {
                viewModel.requestAuthorization()
                viewModel.loadAlarms()
            }
        }
    }
}

@Observable
class AlarmViewModel {
    var alarms: [Alarm] = []
    private let alarmManager = AlarmManager.shared
    
    func requestAuthorization() {
        Task {
            do {
                let state = try await alarmManager.requestAuthorization()
                print("Authorization state: \(state)")
            } catch {
                print("Error requesting authorization: \(error)")
            }
        }
    }
    
    func loadAlarms() {
        Task {
            do {
                let fetchedAlarms = try alarmManager.alarms
                DispatchQueue.main.async {
                    self.alarms = fetchedAlarms
                }
                
                // Start observing alarm updates
                for await updatedAlarms in alarmManager.alarmUpdates {
                    DispatchQueue.main.async {
                        self.alarms = updatedAlarms
                    }
                }
            } catch {
                print("Error loading alarms: \(error)")
            }
        }
    }
    
    func addAlarm(hour: Int, minute: Int, weekdays: Set<Locale.Weekday>) {
        Task {
            do {
                let id = UUID()
                
                // Create schedule
                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))
                ))
                
                // Create presentation
                let alertContent = AlarmPresentation.Alert(
                    title: "Alarm",
                    stopButton: .stopButton,
                    secondaryButton: .snoozeButton,
                    secondaryButtonBehavior: .countdown
                )
                
                let presentation = AlarmPresentation(alert: alertContent)
                
                // Create attributes
                struct EmptyMetadata: AlarmMetadata {}
                let attributes = AlarmAttributes(
                    presentation: presentation,
                    metadata: EmptyMetadata(),
                    tintColor: .blue
                )
                
                // Create configuration with 9-minute snooze
                let configuration = AlarmManager.AlarmConfiguration(
                    countdownDuration: Alarm.CountdownDuration(preAlert: nil, postAlert: 9 * 60),
                    schedule: schedule,
                    attributes: attributes,
                    sound: .default
                )
                
                // Schedule the alarm
                let _ = try await alarmManager.schedule(id: id, configuration: configuration)
            } catch {
                print("Error adding alarm: \(error)")
            }
        }
    }
    
    func toggleAlarm(id: UUID, isPaused: Bool) {
        Task {
            do {
                if isPaused {
                    try await alarmManager.resume(id: id)
                } else {
                    try await alarmManager.pause(id: id)
                }
            } catch {
                print("Error toggling alarm: \(error)")
            }
        }
    }
    
    func deleteAlarms(at indexSet: IndexSet) {
        Task {
            for index in indexSet {
                let alarm = alarms[index]
                do {
                    try await alarmManager.cancel(id: alarm.id)
                } catch {
                    print("Error deleting alarm: \(error)")
                }
            }
        }
    }
}

struct AlarmRow: View {
    let alarm: Alarm
    let viewModel: AlarmViewModel
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(formatTime())
                    .font(.title)
                
                if let schedule = alarm.schedule {
                    Text(formatSchedule(schedule))
                        .font(.caption)
                }
            }
            
            Spacer()
            
            if alarm.state == .paused {
                Button(action: {
                    viewModel.toggleAlarm(id: alarm.id, isPaused: true)
                }) {
                    Text("Resume")
                }
            } else if alarm.countdownDuration != nil {
                Button(action: {
                    viewModel.toggleAlarm(id: alarm.id, isPaused: false)
                }) {
                    Text("Pause")
                }
            }
        }
        .padding(.vertical, 8)
    }
    
    private func formatTime() -> String {
        if let schedule = alarm.schedule, case .relative(let relative) = schedule {
            let hour = relative.time.hour
            let minute = relative.time.minute
            return String(format: "%02d:%02d", hour, minute)
        }
        return "Timer"
    }
    
    private func formatSchedule(_ schedule: Alarm.Schedule) -> String {
        if case .relative(let relative) = schedule, case .weekly(let weekdays) = relative.repeats {
            if weekdays.isEmpty {
                return "One time"
            } else {
                return weekdays.map { $0.description }.joined(separator: ", ")
            }
        }
        return "One time"
    }
}

struct AddAlarmView: View {
    let viewModel: AlarmViewModel
    @Environment(\.presentationMode) var presentationMode
    
    @State private var hour = 8
    @State private var minute = 0
    @State private var selectedDays: Set<Locale.Weekday> = []
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Time")) {
                    DatePicker("", selection: Binding(
                        get: {
                            var components = DateComponents()
                            components.hour = hour
                            components.minute = minute
                            return Calendar.current.date(from: components) ?? Date()
                        },
                        set: { date in
                            let components = Calendar.current.dateComponents([.hour, .minute], from: date)
                            hour = components.hour ?? 8
                            minute = components.minute ?? 0
                        }
                    ), displayedComponents: .hourAndMinute)
                    .datePickerStyle(WheelDatePickerStyle())
                    .labelsHidden()
                }
                
                Section(header: Text("Repeat")) {
                    ForEach([
                        Locale.Weekday.sunday,
                        .monday, .tuesday, .wednesday,
                        .thursday, .friday, .saturday
                    ], id: \.self) { day in
                        Button(action: {
                            if selectedDays.contains(day) {
                                selectedDays.remove(day)
                            } else {
                                selectedDays.insert(day)
                            }
                        }) {
                            HStack {
                                Text(day.description)
                                Spacer()
                                if selectedDays.contains(day) {
                                    Image(systemName: "checkmark")
                                }
                            }
                        }
                        .foregroundColor(.primary)
                    }
                }
            }
            .navigationTitle("Add Alarm")
            .navigationBarItems(
                leading: Button("Cancel") {
                    presentationMode.wrappedValue.dismiss()
                },
                trailing: Button("Save") {
                    viewModel.addAlarm(hour: hour, minute: minute, weekdays: selectedDays)
                    presentationMode.wrappedValue.dismiss()
                }
            )
        }
    }
}

Best Practices

  1. Request Authorization Early: Request AlarmKit authorization when your app first launches or when the user first attempts to create an alarm.

  2. Handle Authorization Denial: Provide a clear path for users to enable permissions if they initially deny them.

  3. Provide Clear Usage Description: Include a clear NSAlarmKitUsageDescription in your Info.plist.

  4. Implement Widget Extension: Always implement a widget extension if your app uses countdown presentations to ensure proper functionality.

  5. Observe Alarm Updates: Use the alarmUpdates async sequence to keep your app's state in sync with the system.

  6. Persist Alarm IDs: Store the UUIDs of alarms you create to manage them later.

  7. Handle Errors Gracefully: AlarmKit operations can throw errors, so implement proper error handling.

  8. Limit Number of Alarms: Be mindful that there's a system limit on the number of alarms an app can schedule.

  9. Customize UI Appropriately: Provide clear and meaningful titles and button labels for your alarms.

  10. Test on Real Devices: AlarmKit behavior, especially notifications and Live Activities, should be tested on physical devices.

References

Source: SKILL.md on GitHub

No alerts6mo4 checks · Risk SAFE
  • Gen Agent Trust Hub7mo

    The skill is a documentation resource for Xcode 26 features and Apple frameworks. It contains reference markdown files with code snippets and architectural guidance. No malicious behavior, obfuscation, or security vulnerabilities were detected.

  • Socket6mo

    No alerts

  • Snyk7mo

    Risk: LOW · No issues

  • Runlayer7mo

    2/21 files flagged

Signed by skilld at de9a679. 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.

Dormantupdated 9 months ago

README badge

README badge for harryworld/xcode26-agent-skills