Takahiro Octopress Blog

-1から始める情弱プログラミング

Push通知の受信起因でWidgetを更新しよう!

はじめに

今回は、iOS14から導入されたWidgetを題材として扱いたいと思います。

既に様々なところで Widget の導入について説明されていますが、筆者が気になったのは、 『プッシュ通知の受信起因で Widget を更新することができるかどうか 』
です。

簡単にサンプルを作成して試してみた結果を備忘録として載せておきたいと思います。

Widgetの更新方法

まず、 Widget の更新方法ですが、これは大きく分けて2種類あります。

  • Widget 自体に定義した更新タイミングで更新する
  • アプリなどから任意のタイミングで更新する

前者の『 Widget 自体に定義した更新タイミングで更新する』は、
Widget ソース内の getTimeline メソッドが該当します。

例えば、1時間ごとに更新したいと思った場合には、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import WidgetKit
import SwiftUI
import Intents

struct Provider: IntentTimelineProvider {
  ...
  func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
      var entries: [SimpleEntry] = []

      // Generate a timeline consisting of five entries an hour apart, starting from the current date.
      let currentDate = Date()
      for hourOffset in 0 ..< 5 {
          let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
          let entry = SimpleEntry(date: entryDate, randomNumber: Int.random(in: 1 ... 10), configuration: configuration)
          entries.append(entry)
      }

      let timeline = Timeline(entries: entries, policy: .atEnd)
      completion(timeline)
  }
  ...
}

struct SimpleEntry: TimelineEntry {
  let date: Date
  let randomNumber: Int
  let configuration: ConfigurationIntent
}

のように定義することで実現できます。

続いて後者の『アプリなどから任意のタイミングで更新する』は、
任意のタイミングで、

1
WidgetCenter.shared.reloadAllTimelines()

を呼び出してやれば良いだけです。

では、プッシュ通知の受信起因で Widget を更新するためには、どうすれば良いのでしょうか?

プッシュ受信契機でWidgetを更新する

答えは、 NotificationService Extension を利用する方法です。

NotificationService Extension はプッシュ通知受信時に条件次第で通知内容を変更したい場合などに利用されます。

NotificationService Extension は下記のようにプッシュ通知の受信を検知してロジックを組み込むことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import UserNotifications
import WidgetKit

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    // プッシュ通知を受信したら、ココを通ります。
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent {
            // Modify the notification content here...
            bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"

            contentHandler(bestAttemptContent)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

この didReceive(_:withContentHandler:) 内で WidgetCenter.shared.reloadAllTimelines() を実行してやれば Widget が更新できます。

(補足)プッシュ通知を簡単に検証する方法について

今回の検証では、node-apnを利用しました。
node-apn を使えば、プッシュ通知に必要な AuthKey を用意し、多少の実装をするだけ簡単にプッシュ通知を検証できるのでオススメです。

実装は、プッシュ通知をnode-apnで送ってみよう!で詳しく説明していますので、ご参照ください。
NotificationService Extension を利用するので、ペイロードに "mutable-content": 1 が必要になることだけ注意してください。

1
2
3
4
5
6
var note = new apn.Notification();
note.badge = 3;
note.sound = "ping.aiff";
note.alert = "プッシュ通知が届きましたよ!";
note.topic = "com.xxxx.WidgetSample";
note.mutableContent = 1 // ココで "mutable-content": 1 を指定しています。

まとめ

さて如何でしたでしょうか?
Widget はiOS14から導入され、ホーム画面に多大な影響を与える期待の新機能ですので、
ユーザに更なる利便性を与えるためにも積極的に導入していきたいですね。

といったところで本日はここまで。

Comments