2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

爆速でAlarmKitを使ったアプリを開発してみた

2
Posted at

今回作ったアプリ

こんにちは!Life is Tech!でiPhoneメンターをやってるよみです!
今回はカレンダー×アラームのコンセプトでアプリを作りました。
ずっとカレンダーでアラームのスケジューリングをしたかったんですけど、ついにAlarmKitが出たということで作ってみました。

時間がなさすぎる

気づいたらアドカレの担当日になってしまい、まだ何もテーマが決まっていませんでした。
だったらずっと作りたかったアプリを作って記事にすればいいじゃない!ということで、AIが発達した現代で超短期間1人ハッカソンを実施しました。
と言っても現在時刻15:00、、、果たして間に合うのか

プロジェクト作成

とりあえずプロジェクトを作成して、以下のプロンプトを投げてAIにベースのアプリを作ってもらいました。

簡単なカレンダーアプリを作って

AlarmKitに関する実装はまだ発表されたばかりでうまく行かなそうだったので、まずカレンダーアプリを作ってもらうことにしました。
そしていくつか細かい仕様に関する質問に答えて、待つこと10分、、、

すでにカレンダーアプリが出来上がっていました。

あんな簡単なプロンプトでこのクオリティができることに感動。

スワイプで月を移動するとか、日付のセルに予定を表示するとかは追加のプロンプトで実装してもらいました。

起床時刻を設定できるようにする

ただいまの時刻16:30です。この調子でいけば間に合うか???

まずは起床時刻を設定できるようにします。
そして設定した時刻を日付の横に表示できるようにします。
これらも全部AIにお願いしました。

アラームを鳴らせるようにする

ここからが本番ですね
まずはInfo.plistに、一番下のやつを追加します
image.png

そしてユーザーにアクセスをリクエストする処理を書きます。

AlarmManagerExtensions.swift
import AlarmKit

extension AlarmManager {
    // アラームを鳴らすための権限をリクエスト
    func requestAlarmAuth() async {
        do {
            let manager = AlarmManager.shared
            let status = try await manager.requestAuthorization()
        } catch {
            print("アラームの権限のリクエストに失敗")
        }
    }
    
    // アラームを鳴らす権限が許可されているかをチェックする
    func checkAuthorization() -> Void {
        switch AlarmManager.shared.authorizationState {
        case .authorized:
            print("許可済み")
        case .notDetermined:
            print("まだリクエストしていません")
            Task {
                await requestAlarmAuth()
            }
        case .denied:
            print("許可されていません")
        @unknown default:
            print("Unknown")
        }
    }

こんな感じでリクエストの関数を作成
権限のチェックをしたときに、まだリクエストしていなかったら、リクエストするようにしておく。

ContentView.swift
+ import AlarmKit
import SwiftUI
import SwiftData

struct ContentView: View {
    @State private var selectedDate = Date()

    var body: some View {
        MonthCalendarView(selectedDate: $selectedDate)
+           .onAppear {
+               AlarmManager.shared.checkAuthorization()
+           }
    }
}

#Preview {
    ContentView()
        .modelContainer(for: [CalendarEvent.self])
}

これでアプリを開いたときに、権限がリクエストされていなかったら、許可を求めるアラートが出るようになります。

アラームを鳴らす関数を作る

AlarmManagerExtensions.swift
import AlarmKit
+ import SwiftUI

extension AlarmManager {
    func requestAlarmAuth() async {
        ...
    }
    
    func checkAuthorization() -> Void {
        ...
    }
    
+    func scheduleAlarm(id: UUID, date: Date) async throws {
+        typealias AlarmConfiguration = AlarmManager.AlarmConfiguration<AlarmData>
+ 
+        let scheduleFixed = Alarm.Schedule.fixed(date)
+        
+        let attributes = alarmAttributes()
+        
+        let alarmConfiguration = AlarmConfiguration(
+            schedule: scheduleFixed,
+            attributes: attributes,
+        )
+        
+        try await AlarmManager.shared.schedule(id: id, configuration: alarmConfiguration)
+    }
    
+    private func alarmAttributes() -> AlarmAttributes<AlarmData> {
+        let alertPresentation = AlarmPresentation.Alert(
+            title: "Good Morning!",
+        )
+        
+        let attributes = AlarmAttributes<AlarmData>(
+            presentation: AlarmPresentation(
+                alert: alertPresentation),
+            tintColor: Color.green)
+        
+        return attributes
+    }
}

alarmAttributesは、アラームが鳴ったときに表示される画面を作っています。

そして、scheduleAlarmの引数にアラームを鳴らしたい時刻を入れるとその時間にアラームが鳴ってくれます。
idはSwiftDataで保存しているものと紐付けるために、外から受け取れるようにしています。

アラームを鳴らしてみる

WakeUpTimeFormView.swift
    private func saveWakeUpTime() async {
        do {
            if let existing = existingWakeUpTime {
                try? AlarmManager.shared.cancel(id: existing.id)
                existing.time = selectedTime
                try await AlarmManager.shared.scheduleAlarm(id: existing.id, date: existing.alarmDate())
                existing.updatedAt = Date()
            } else {
                let wakeUpTime = WakeUpTime(date: date, time: selectedTime)
                try await AlarmManager.shared.scheduleAlarm(id: wakeUpTime.id, date: wakeUpTime.alarmDate())
                modelContext.insert(wakeUpTime)
            }

            try? modelContext.save()
        } catch {
            print("アラームを設定できませんでした。:\(error)")
        }
    }

アラームの時刻を設定する関数の中に、アラームを予約する関数を埋め込みました。
すでに一度アラームが設定されている場合は、前のアラームをキャンセルしてから再度予約します。

無事アラームが鳴るようになりました。

スヌーズをつけたい

一度夜ご飯を食べ、現在19:10
いい感じになってきました。
しかし必ずと言っていいほど二度寝をする私は、スヌーズ機能が絶対に欲しい。
時間が迫ってるけど絶対に欲しい。
というわけで頑張っていきます

まず、スヌーズボタンを追加していきます。

AlarmManagerExtensions
    private func alarmAttributes() -> AlarmAttributes<AlarmData> {
+        let snoozeButton = AlarmButton(
+            text: "Snooze",
+            textColor: .white,
+            systemImageName: "repeat.circle")
        
        let alertPresentation = AlarmPresentation.Alert(
            title: "Good Morning!",
+            secondaryButton: snoozeButton,
+            secondaryButtonBehavior: .countdown
        )
        
        let attributes = AlarmAttributes<AlarmData>(
            presentation: AlarmPresentation(
                alert: alertPresentation),
            tintColor: Color.green)
        
        return attributes
    }

そしてアラームを再設定する関数を定義します。
これはDateを使って絶対時間で設定していたさっきの関数と違って、何秒後にアラームを鳴らすか、相対時間で設定する関数になります。

AlarmManagerExtensions.swift
    func scheduleAlarm(id: UUID, preAlertSecond: Double, postAlertSecond: Double = 5) async throws {
        typealias AlarmConfiguration = AlarmManager.AlarmConfiguration<AlarmData>
        
        let countdownDuration = Alarm.CountdownDuration(preAlert: preAlertSecond, postAlert: postAlertSecond)
        
        let attributes = alarmAttributes()
        
        let alarmConfiguration = AlarmConfiguration(
            countdownDuration: countdownDuration,
            attributes: attributes)
        
        let alarm = try await AlarmManager.shared.schedule(id: id, configuration: alarmConfiguration)
        print(alarm)
    }

preAlertSecondは再度アラームがなるまでの時間ですが、postAlertSecondはよくわかりませんでした。
(ドキュメントには再びカウントダウン状態に戻るまでと書いてありましたが、設定した時間が過ぎてもなり続けていたのでよくわかりません。)

次に、AppIntentのLiveActivityを使っていきます

SnoozeIntent.swift
import AppIntents
import AlarmKit

struct SnoozeIntent: LiveActivityIntent {
    @Parameter(title: "alarmID")
    var alarmID: String
    
    static var title: LocalizedStringResource = "Snooze"
    
    func perform() async throws -> some IntentResult {
        guard let id = UUID(uuidString: alarmID) else {
            print("Invalid UUID string: \(alarmID)")
            return .result()
        }
        Task {
            try AlarmManager.shared.stop(id: id)
            try await AlarmManager.shared.scheduleAlarm(id: id, preAlertSecond: 10, postAlertSecond: 10)
        }
        return .result()
    }
    
    init(id: UUID) {
        self.alarmID = id.uuidString
    }
    
    init() {
        self.alarmID = ""
    }
}

アラームのIDを基に鳴っているアラームをストップし、新しくアラームを再設定することでスヌーズ機能を実装しています。

また、Intentを使っているということで、一度UUIDをStringに直してから受け取るようにしています。

最後にIntentをボタンに設定します

AlarmManagerExtensions.swift
    func scheduleAlarm(id: UUID, date: Date) async throws {
        typealias AlarmConfiguration = AlarmManager.AlarmConfiguration<AlarmData>

        let scheduleFixed = Alarm.Schedule.fixed(date)
        
        let attributes = alarmAttributes()
        
        let alarmConfiguration = AlarmConfiguration(
            schedule: scheduleFixed,
            attributes: attributes,
+            secondaryIntent: SnoozeIntent(id: id)
        )
        
        let alarm = try await AlarmManager.shared.schedule(id: id, configuration: alarmConfiguration)
        print(alarm)
    }
    
    func scheduleAlarm(id: UUID, preAlertSecond: Double, postAlertSecond: Double = 5) async throws {
        typealias AlarmConfiguration = AlarmManager.AlarmConfiguration<AlarmData>
        
        let countdownDuration = Alarm.CountdownDuration(preAlert: preAlertSecond, postAlert: postAlertSecond)
        
        let attributes = alarmAttributes()
        
        let alarmConfiguration = AlarmConfiguration(
            countdownDuration: countdownDuration,
            attributes: attributes,
+            secondaryIntent: SnoozeIntent(id: id)
        )
        
        let alarm = try await AlarmManager.shared.schedule(id: id, configuration: alarmConfiguration)
        print(alarm)
    }

これでスヌーズができるようになりました!

なんとか間に合いそうでよかった!!!

最後に

ここまでみてくださってありがとうございました!
時間がなくてちゃんと説明できなかったので、細かくはGithubを見てください!
サンプルコードはこちらから

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?