[iOS] [Swift] ローカル通知とアプリのライフサイクルについて


はじめに

これはiOS アプリでプッシュ通知についての記事です。

これから学ぶ知識

  • 通知
  • AppDelegate / SceneDelegate
  • UNNotificationContent
  • UNNotificationTrigger
  • UNNotificationRequest

通知

概要

スマートフォンを使っていると 1 日に 1 度はみることのある通知は、ユーザーがアプリを開いていなくてもアプリに関する情報を伝えられるというメリットが強力です。

例えば、アラームアプリはユーザーがアプリを開いていなくてもその時刻が来たことを適切に伝えてくれます。

また、通知にはいくつかの種類がありますが、よく使われるのは以下のものです。

アラート(リスト) アラート(バナー)
バッジ サウンド
No Image

アプリケーションのライフサイクル

プログラム上では通知を受け取った際のアプリの状態によって処理が変わってきます。
状態というのはアプリが今起動しているかアプリが画面上に表示されているかといったもののことです。

そこで、通知と紐付けて、iOS アプリケーションにおけるライフサイクルを確認しましょう。
ライフサイクルとは生まれてから消えるまでの過程のことです。

フォアグラウンド(Foreground)

フォアグラウンドとはアプリケーションが画面上に表示されている状態のことを指します。

Active / In Active の 2 種類に細かく分けられます。

バックグラウンド(Background)

バックグランドとはアプリケーションが画面上に表示されていない状態のことを指します。

Backgroundの状態で一定時間起動が続くと、システムが自動的にSuspended(停止状態)に切り替えてくれます。

アプリケーションのライフサイクル
種類 説明
Active アプリが現在操作中である 使用中
InActive アプリの操作から一時的に離れている状態であるが、アプリは表示されている コントロールセンター・通知センターを表示している状態
Foreground ActiveInActiveの状態の総称
Background 別のアプリケーションが表示されている状態。アプリは起動していてバックグランドで特定のタスクが可能
Suspended 一定時間のBackground状態が続くと自動的にこの状態になる
Not Running アプリが完全に停止している状態

細かく分けられるものの、アプリケーションにはフォアグラウンドバックグラウンドの状態があることが重要です。
SuspendedNot Runningに関してはシステムが自動で管理する部分もありあまり開発者でも意識することはありません。

AppDelegate について

AppDelegate とは@UIApplicationMain の属性をもつクラスで、アプリごとに 1 つのみ存在します。

SceneDelegateについて

SceneDelegateとはAppDelegateとは異なり、アプリごとに複数存在することがあります。iOS13から導入されたMultiple Windowsによって一つのアプリを複数のウィンドで操作できることがあるため、アプリケーションのライフサイクルはAppDelegateよりもSceneDelegateの方が優先されるようになりました。(UIApplicationDelegate#applicationDidBecomeActiveはSceneDelegateが設定されている場合は呼ばれません。)

例えば、アプリがActiveなのかそうでないのかを判断する場合は以下の2つのメソッドを実装する必要があります。

  • func sceneDidBecomeActive(_ scene: UIScene)
    • シーンのライフサイクルがアクティブになった際に呼ばれます。
  • func sceneWillResignActive(_ scene: UIScene)
    • シーンのライフサイクルがアクティブでなくなった際に呼ばれます。

許可について

通知はアプリをインストールしてくれた全てのユーザーに対して送ることができるわけではありません。

実際に通知を送るには下の画像の様に予めユーザーに許可をとる必要があります。

通知許可アラート

通知の許可を送るコード

以下のコードでアラート バッジ サウンドの3つに対して許可のリクエストをすることができます。

// プッシュ通知の許可を依頼する際のコード
UNUserNotificationCenter.current().requestAuthorization([.alert, .badge, .sound]) { (granted, error) in
    // [.alert, .badge, .sound]と指定されているので、「アラート、バッジ、サウンド」の3つに対しての許可をリクエストした
    if granted {
        // 「許可」が押された場合
    } else {
        // 「許可しない」が押された場合
    }
}

通知を送信する

UNMutableNotificationContent

通知を送信する際にUNMutableNotificationContentというクラスを作成します。

下の例は、その通知によって付与されるバッジの数・通知のタイトル・通知を受信した際のサウンドを決定しています。

// MARK: 通知の中身を設定
let content: UNMutableNotificationContent = UNMutableNotificationContent()
content.title = notificationTitle
content.sound = UNNotificationSound.default
content.badge = 1

UNNotificationTrigger

次にUNNotificationTriggerというクラスを作成します。
UNNotificationTriggerはトリガーという言葉が含まれていることから分かる様に、いつ通知を発行するのかを設定するためのものです。
実際には以下の4つのクラスを使用してトリガーを作成します。

  • UNCalendarNotificationTrigger
    • カレンダーを元にするトリガー
    • DateComponentsを用いて特定の時間帯になると通知が発行される
    • 次の朝9時
    • 毎朝9時
  • UNLocationNotificationTrigger
    • 位置を元にするトリガー
    • ある特定の地域に位置していると通知が発行される
  • UNTimeIntervalNotificationTrigger
    • 時間差を元にするトリガー
    • 60秒後
    • 1週間後
  • UNPushNotificationTrigger
    • リモート通知におけるトリガー

作成例

ある日付データnotificationDateに対して年/月/日/時間/分が同じ際に一回のみ通知される際のトリガーは以下のように書けます。

// MARK: 通知をいつ発動するかを設定
// カレンダークラスを作成
let calendar: Calendar = Calendar.current
let trigger: UNCalendarNotificationTrigger = UNCalendarNotificationTrigger(dateMatching: calendar.dateComponents([.year, .month, .day, .hour, .minute], from: notificationDate), repeats: false)

UNNotificationRequest

次にUNNotificationRequestというクラスを作成します。

UNNotificationRequestは先ほどのUNNotificationContentUNNotificationTriggerの二つを用いて作成することができます。

作成例

// MARK: 通知のリクエストを作成
let request: UNNotificationRequest = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

identifierにはUUID().uuidStringという一意の(重複して生成されることのない)文字列を使用します。通知ごとにユニークで他と被ることがない必要があります。

通知を登録(送信)する

最後に作成した通知リクエストを通知センターに登録して、通知を送信します。
コールバック引数の中で、実際にエラーが返ってきたかどうかをerror: Error?引数によって判断することができます。

// MARK: 通知のリクエストを実際に登録する
UNUserNotificationCenter.current().add(request) { (error: Error?) in
    // エラーが存在しているかをif文で確認している
    if error != nil {
        // MARK: エラーが存在しているので、エラー内容をprintする
    } else {
        // MARK: エラーがないので、うまく通知を追加できた        
    }
}

通知を受け取った際の処理を実装する

設定した時刻になり、ユーザーが通知を受信したときの処理をする必要があります。

UNUserNotificationCenterDelegateに準拠した上で、以下の 2 つのメソッドを実装する必要があります。

class ClassA: UNUserNotificationCenterDelegate {
    // フォアグラウンドの状態でプッシュ通知を受信した際に呼ばれるメソッド
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.banner, .list])
    }

    // バックグランドの状態でプッシュ通知を受信した際に呼ばれるメソッド
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        completionHandler()
    }
}

通知を受け取った際に、アプリが画面に表示されている(フォアグラウンド)か、そうではない(バックグランド)かで、別のメソッドを実装する必要があることが分かると思います。

頭にあるClassA は例で、実際には以下のようにAppDelegateUNUserNotificationCenterDelegateを準拠して 2 つのメソッドを実装します。

extension AppDelegate: UNUserNotificationCenterDelegate {
    // フォアグラウンドの状態でプッシュ通知を受信した際に呼ばれるメソッド
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.banner, .list])
    }
    // バックグランドの状態でプッシュ通知を受信した際に呼ばれるメソッド
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        completionHandler()
    }
}

具体的なサンプル

+ボタンから作成したプッシュ通知を一覧画面で確認できるアプリです