【SwiftUI】ローカル通知

Swift

SwiftUIアプリで作ってるので一応タイトルにSwiftUIと付けていますが、
通知部分は大した他と変わりません。

SwiftUI的な部分はObservableObjectに持たせてEnvironmentObjectで使ってる程度のもので、
正直好きなところに好きなように持たせれば良いと思います。

通知からDeepLinkで起動する記事を書く予定だったのですが、
折角なので通知で分けて先に読んでもらった方が分かりやすいかと思って、
この記事を書くことにしました。

View

import SwiftUI

@main
struct NotificationTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(NotificationModel())
        }
    }
}

struct ContentView: View {
    
    @EnvironmentObject var notificationModel:NotificationModel
    
    var body: some View {
        VStack {
            Button(action: {
                self.notificationModel.setNotification()
            }, label: {
                Text("SetNotification")
            }).padding()
            
            Button(action: {
                self.notificationModel.removeNotification()
            }, label: {
                Text("RemoveNotification")
            }).padding()
        }
    }
}

非常に単純ですね。今回は通知を出すボタンを削除するボタンのみです。

通知系は全てNotificationModelの中でやっているのですが、この位置で問題ありません。
AppDelegateでやっている例もあると思いますが、このままで次回のDeepLinkも問題なく動くので、
UIApplicationDelegateAdaptorを使う必要はありません。

DelegateClass

今回はフォアグラウンド通知の為に作成しました。
バックグラウンドのみで出すだけであれば不要です。
通知がタップされた時のメソッドもここに書くことになります。

import UserNotifications

//フォアグラウンド通知用、バックグラウンドのみなら不要
class ForegroundNotificationDelegate:NSObject,UNUserNotificationCenterDelegate{
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        //completionHandler([.alert, .list, .badge, .sound]) //~iOS13
        completionHandler([.banner, .list, .badge, .sound]) //iOS14~
    }
}

NotificationModel

import UserNotifications

class NotificationModel: ObservableObject {

    let notificationIdentifier = "NotificationTest"
    var notificationDelegate = ForegroundNotificationDelegate()

    init() {
     //フォアグラウンド通知用、バックグラウンドのみなら不要
        UNUserNotificationCenter.current().delegate = self.notificationDelegate
    }

    func setNotification(){

        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .sound, .badge]){
            (granted, _) in
            if granted {
                //許可
                self.makeNotification()
            }else{
                //非許可
            }
        }

    }

    func makeNotification(){

        //日時
        let notificationDate = Date().addingTimeInterval(10)//10秒後
        let dateComp = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: notificationDate)

        //日時でトリガー指定
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComp, repeats: false)

        //通知内容
        let content = UNMutableNotificationContent()
        content.title = "NotificationTitle"
        content.body = "NotificationBody"
        content.sound = UNNotificationSound.default

        //リクエスト作成
        let request = UNNotificationRequest(identifier: self.notificationIdentifier, content: content, trigger: trigger)

        //通知をセット
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    }

    func removeNotification(){
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [self.notificationIdentifier])
    }
}

変数とイニシャライザ(デリゲートのセット)

次に通知関連をまとめたクラスです。
変数とイニシャライザから

    let notificationIdentifier = "NotificationTest"
    var notificationDelegate = ForegroundNotificationDelegate()

    init() {
        UNUserNotificationCenter.current().delegate = self.notificationDelegate
    }

通知の作成や削除時にIdentifierが必要になります。
Stringで指定されているので今回は単純な名前をつけました。
自分で指定するので有れば何でも良いと思いますが、
ユーザーがデータを作成する場合などはUUIDを使うと良いと思います。

delegate calssはどこかで保持しておかないと行けないのでここで持ちます。
model自身にUNUserNotificationCenterDelegateを継承してselfを渡すパターンも良く見ますが、
フォアグラウンド通知をON・OFF出来る様にする場合、別な物を渡す必要があるので、
私は変数で持たせておく方が好みです。

//良くあるやつ
class NotificationModel: NSObject, ObservableObject,UNUserNotificationCenterDelegate {

    let notificationIdentifier = "NotificationTest"

    override init() {
        super.init()

        UNUserNotificationCenter.current().delegate = self
    }
}

通知の許可

今回は通知をセットする時に許可を求めています。初回のみダイアログが出ます。
非許可を選択された場合は端末の設定から変更して貰う必要があります。
通知が重要なアプリで非許可にされた場合はアラートなどでメッセージ出すなどして誘導しましょう。

    func setNotification(){

        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .sound, .badge]){
            (granted, _) in
            if granted {
                //許可
                self.makeNotification()
            }else{
                //非許可
            }
        }

    }

通知の作成

日時で指定して通知を出しています。
identifierが同じだと上書きされるので複数出すときは気をつけましょう。
requestの中身は通知を受け取った際の処理で参照できます。今回は使ってませんが。

func makeNotification(){

        //日時
        let notificationDate = Date().addingTimeInterval(10)//10秒後
        let dateComp = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: notificationDate)

        //日時でトリガー指定
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComp, repeats: false)

        //通知内容
        let content = UNMutableNotificationContent()
        content.title = "NotificationTitle"
        content.body = "NotificationBody"
        content.sound = UNNotificationSound.default

        //リクエスト作成
        let request = UNNotificationRequest(identifier: self.notificationIdentifier, content: content, trigger: trigger)

        //通知をセット
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
    }

通知の削除

最後に削除ですが、identifierを配列で渡すだけです。

    func removeNotification(){
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [self.notificationIdentifier])
    }

最後に

通知を出すだけで有れば極端に難しいと言うことはないと思います。
ただ、このままだと通知を出した際にアプリのトップ画面が表示されます。
SceneStorageを使用すると直前の画面が表示されますが、
実際に通知から遷移して欲しい画面とは限りません。

【SwiftUI】SceneStorageで画面遷移状態の維持
SceneStorageで画面遷移状態の維持が簡単に出来ます。Appleのアプリ状態の維持に関するドキュメントとサンプルはこちら。 そのままだと結構色々やってあるので、今回はNavigationLinkだけ抜き出して最小限の構成で紹介します...

画面遷移が多いアプリの場合はユーザービリティが下がるので、
次回公開予定ののDeepLinkでの画面遷移も是非見てみて下さい。

コメント

  1. […] […]

タイトルとURLをコピーしました