Push NotificationsをCloudKitで使ってみる(CoreData 手習い#9)

Push NotificationをColudKitで使ってみるCloudKit

はじめに

前回はCloudKitを使ってクラウド経由でデータの共有を実現してみました。想像以上に簡単にできて驚きです。

とはいうものの、XCodeのコンソールに大量のログが表示されているのが気になります。これを放置して良いものかどうか。

このログの中で気になったpush notificationsに挑戦してみようという次第です。

ログを流し読みする

ログは大量ですが、時系列で見ると1回で一気に吐き出されています。

区分っぽいものは、

  • SimulatedCrash:
  • CoreData: warning: CoreData+CloudKit:
  • CoreData: debug: CoreData+CloudKit:
  • CoreData: CloudKit: CoreData+CloudKit:

の4区分ですね。

この内、気になるのは最初の2つ。後半2つはおそらく参考情報のようなものだと思います。バックグラウンドでデータの同期を取りましたよ、とか同期した内容はこうですよ、とか。

SimulatedCrashを詳しく見る

何を指摘されたのか

CloudKit push notifications require the 'remote-notification' background mode in the info plist

CloudKit push notificationsなんて、前回の実装で見覚えが無いんですけどね。

ちなみにCloudKitでPush通知に関するもののようです。データの変更が起きたときに各端末に情報が飛ぶのか……

push notificationsを調べる

まぁ、指摘されるということは何かあるのでしょう。そういえば、どこかで見たような気がする。

signing&CapabilitiesにPushNotificationsの項目がある図

居た!

push notificationsが無いときはどうなるのか

試しに、Capabilitiesから削除して実行してみました。

結果は変わらず。CloudKitを有効にした時点で、裏でpush notificationsを使う前提になっているんでしょうか。

CloudKit push notificationsを実装する

せっかくです。この際実装してみましょう。

push notificationsを元に戻す

さっき消してしまったので、もう一度有効にしておきます。

CapabilitiesダイアログでPushNotificationを選択する
+Capabilityボタンより

Background Modesを設定する

CapabilitiesでBackground Modesを選択すえう
+Capabilityボタンより

SimulatedCrashbackground modeという文言が出てましたね。

CloudKit push notifications require the 'remote-notification' background mode in the info plist

Capabilitiesに追加して、Remote notificationにチェックを入れておきましょう。

Background ModesにてRemote notificationにチェックを付ける

ユーザに許可を求める

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { authorized, error in
    if authorized {
        DispatchQueue.main.async(execute: {
            application.registerForRemoteNotifications()
        })
    }
})

私こんなことしますけど良いですか表明と同意確認ですね。アプリ起動時に行ったほうが良いので、AppDelegate.swiftdidFinishLaunchingWithOptionsに入れました。

import UserNotificationsをお忘れなく。

通知を受けたときの処理を実装する

UNUserNotificationCenterDelegateプロトコルを実装します。それぞれでやっていることの解釈はおそらく有ってるような気がする。

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    //通知を表示しようとするときの処理をここへ
  }
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    //通知を受け取ったときの処理をここへ    
  }

通知を出すときの処理を実装する

受けとりができるようになれば次は通知を出す処理です。

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let subscription = CKQuerySubscription(recordType:{レコードタイプ}, predicate: NSPredicate(format: "TRUEPREDICATE"), options: [.firesOnRecordUpdate,.firesOnRecordCreation,.firesOnRecordDeletion])
    let info = CKSubscription.NotificationInfo()
    info.titleLocalizationKey = "%1$@"
    info.titleLocalizationArgs = ["name"]
    info.alertLocalizationKey = "%1$@"
    info.alertLocalizationArgs = ["attendance_time"]
    info.shouldBadge = true         //通知数をアプリアイコンの右肩に出すかどうか
    info.soundName = "default"      //通知音はどうするか
    subscription.notificationInfo = info
    CKContainer.default().privateCloudDatabase.save(subscription, completionHandler: { subscription, error in
        if error == nil {
        } else {
        }
    })
}

レコードタイプはCloudKit Dashboardにて確認を!

CloudKit DashboardのDataを選択する
Dataをクリック
左ペインの中央あたりにレコードタイプ(Type)があります
左ペインの真ん中あたり

predicateには通知を発出したいレコードの条件NSPredicateを指定します。

通知で表示する情報はCKSubscription.NotificationInfo()に詰め込みます。

info.titleLocalizationKey = "%1$@"
info.titleLocalizationArgs = ["name"]

についてですが、titleLocalizationArgsにはエンティティのフィールド名を指定します。暗号の如きtitleLocalizationKeyの値ですが、これはArgs1つ目のフィールドの値を示しています。

info.titleLocalizationKey = "%2$@"
info.titleLocalizationArgs = ["name", "address"]

このようにすると、Args2つ目のフィールドの値を指すことになります。

いよいよ実行

ワクワク

ユーザへの同意

これはクリア

通知を出してよいかユーザに確認を取る

通知が来るか!?

結果:来ませんでした。

クラウドにはデータが保存されていましたが、通知が飛ばない。ログを見ると1 errorの文字が。

エラー修正(2件)

フィールド名を間違えていた

info.titleLocalizationKey = "%1$@"
info.titleLocalizationArgs = ["name"]

CloudKit Dashboardを見るとこのようなフィールドはありませんでした。

早合点よくない。xcdatamodeldファイルで設定したフィールド名と若干違っていました。レコードタイプの確認のときにセットで見ておくべきでしたね。
XCode先生に面倒事を任せっきりにしてしまったゆえのポカミスです。先生との意思疎通大事。

ターゲットとするデータベースを間違えていた

今参照しているデータベースはプライベートの方です。

CKContainer.default().publicCloudDatabase.save(subscription, completionHandler: { subscription, error in

おわかりいただけただろうか。正しくは、

CKContainer.default().privateCloudDatabase.save(subscription, completionHandler: { subscription, error in

です。

リベンジします

Insertボタンをポチッとな。

無事に通知が表示されました

でました!

気になる

publicCloudDatabaseの扱い方

メソッド上はpublicCloudDatabaseの方も扱えそうな雰囲気がします。今回はエラーが出てしまいましたが、これの対応も見ていきたいと思います。

ユーザにイベントの一斉通知とかできそう。

Background fetchとな!

Background Modesの下から3番めにBackground fetchという項目があります
下から3番めに居ます

そっと裏でローカルDBの更新とかできそう。

確認の際に表示する文言を設定する

さっと流しましたが、おそらくinfo.plistファイルで設定できるはず。

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