iOS
Swift
UNNotificationContentExtension
AppGroup

UNNotificationContentExtension で 画像をダウンロードしたり、AppGroupでアプリ本体と情報共有したり

httpで通信してて工数殺したので自戒の念を込めて書いてたんですが、
Extensionで画像をダウンロードしてーってQiita無いなって思ったのでそれも含めて書いてます。
なお、今更HTTPを使うなというのがAppleの意向なのでそれに従う必要があります。1

あと、AppGroupsをこのExtensionで使ったってQiitaもないのでTIPSにいれておきました。

httpsを使わないと

UNErrorDomainが Throw されます。

前提

  • 既に UNNotificationContentExtension を使ったものが実装されてる

実装

  • 画像ファイルは存在している.サーバー側に問題は無い。
  • Extensionじゃない本体では、 App Transport Security Settings を設定している
    • 本体側ではhttp通信をしている箇所がある
  • import UserNotifications import UIKit だけ使いたい。
  • 画像(png/jpg)をダウンロードするコードは以下
myNotificationExtension.swift
import UserNotifications
import UIKit

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent = UNMutableNotificationContent.init()

    // ...
    // 割愛
    // ...


    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
      self.contentHandler = contentHandler
      bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)!
      let host = "https://abcdefg-hogetarou.tv" // httpだと出来ない!!!
      let image = "/terebibangumi.png"
      let fileManager = FileManager.default
      let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
      let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
      let session = URLSession(configuration: URLSessionConfiguration.default)
      let task = session.dataTask(with: URL(string: host + image)!, completionHandler: { (data, response, error) in
          do {
              self.bestAttemptContent.title = "\(self.bestAttemptContent.title)"
              try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
              let imageFileIdentifier = "banbumi.png"
              let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
              try data?.write(to: fileURL)
              let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: nil)
              self.bestAttemptContent.attachments = [imageAttachment]
          } catch let e {
              self.bestAttemptContent.body = "\(String.init(describing: e))"
          }
          contentHandler(self.bestAttemptContent)
      })
      task.resume() 
      // ....
      // 割愛
      // ....

☝️ 色んな者を do で囲むのは Exception 投げられたときに追いづらくなる可能性があるので、上記のようにせずきちんと分けることをオススメします。

TIPS

  • エラーの時にタイトルを変えられるが、ユーザーにエラーがおこってるな?と思われてはいけない(と思うので)タイトルの最後に . とか , とか付けてデバッグデバッグ出来るようにしてる。
    • 上記では、 eをそのままbodyにいれている。これは開発用なので参考にする人がいれば注意.
  • 画像がhttp通信が原因で、Exceptionが投げられると UNErrorDomainInvalid attachment file URL みたいなのが届く。HTTPSを使えば解消できる。

アプリ本体と情報共有

AppGroupsを使えば、アプリ本体から情報をもらうことができる。

 1. Developer Center で App Groups を定義

AppGroup.png

2. Xcodeで機能を有効にして、AppGroupを追加する

Xcode.png

3. 実装する

取得する方法:

myNotificationExtension.swift
let groupName = "group.terebi.dayo"
if let userDefaults = UserDefaults(suiteName: groupName) {
    let key = "video_killed_the radio_star"
    userDefaults.synchronize() //念のため
    let savedCount = userDefaults.integer(forKey: key) // 取れる yay
    // 割愛

設定する方法:

Interactor.swift
let groupName = "group.terebi.dayo"
if let userDefaults = UserDefaults(suiteName: groupName) {
    let key = "video_killed_the radio_star"
    userDefaults.set(0, forKey: key) // 設定できる yay
    userDefaults.synchronize() //念のため
}

最後に

3ヶ月前にも同じ事をやってこの現象の原因は知っていたが、今回も1日掛かってしまった。
ちなみに前回は3日間かかった。
自戒の念を込めてTIPSを残す。


  1. ATS使えば通信できるかも??かも??でも明日には使え無くなる可能性有るかも??