iOS 10 での通知処理について

今更ながら、iOS の通知表示をカスタマイズできるようになったことを調べていたら、まったく変わっていたので、個人的に整理する。

# 準備
* APNs SSL 証明書の作成

![RemoteNotificationDeveloperPortal.png](https://qiita-image-store.s3.amazonaws.com/0/134810/97dc2524-f32a-5567-6af5-915ccc0863db.png)

Apple Developer のサイトにて対象となるアプリの `Push Notification` を有効にして開発用(Development)と製品用(Production)のSSL証明書を作成します。
ここで作成したSSL証明書を使ってメッセージを送信するので、Google Firebase や Amazon SNS などに登録しておきます。

* プロジェクトの設定

![ProjectCapabilities.png](https://qiita-image-store.s3.amazonaws.com/0/134810/c917d33f-26fb-83f4-99f5-68145c662e44.png)

このようにプロジェクトの `Capabilities` にある `Push Notifications` を有効にします。

環境によっては、 `Provisioning Profile` が対象のものとは違うものが設定されることがあるので、`Signing` の `Automatically manage signing` を無効にして個別に設定します。

![ProjectSigning.png](https://qiita-image-store.s3.amazonaws.com/0/134810/9197acd3-ff3c-3cdf-cc27-c349861e7135.png)

# 実装
## プッシュ通知

Google Firebase の [サンプル](https://github.com/firebase/quickstart-ios/blob/master/messaging/MessagingExampleSwift/AppDelegate.swift) などを参考にすると、以下のようなコードになると思います。
大きく変わったのは `UserNotifications` を使うようになったこと。

```swift:AppDelegate.swift
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        if #available(iOS 10.0, *) {
            // iOS 10 以降の設定
            UNUserNotificationCenter.current().delegate = self
            
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            // iOS 10 より前の設定
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
        
        application.registerForRemoteNotifications()

        return true
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
        // アプリが起動している間に通知を受け取った場合の処理を行う。
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {        
        // アプリがバックグラウンド状態の時に通知を受け取った場合の処理を行う。
        completionHandler(UIBackgroundFetchResult.newData)
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        // システムへのプッシュ通知の登録が失敗した時の処理を行う。
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // Device Token を取得した時の処理を行う。
        print("APNs token retrieved: \(deviceToken)")
    }
}

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
    
    // iOS 10 以降では通知を受け取るとこちらのデリゲートメソッドが呼ばれる。
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        completionHandler()
    }
}
```

## ローカル通知
ローカル通知を設定するには、以下のクラスを利用します。

* UNMutableNotificationContent  
これで通知する内容を設定する。  
* UNNotificationTrigger  
これで通知する条件を設定する。  
* UNNotificationRequest  
これで通知のリクエストを作成する。

通知の条件で指定できるものは以下の三つがあります。

* `UNTimeIntervalNotificationTrigger`  
経過時間を指定する。
* `UNCalendarNotificationTrigger`  
日時を指定する。
* `UNLocationNotificationTrigger`  
場所を指定する。


５秒後に通知を行いたい場合は、以下のようなコードになります。

```swift:NotificationManager.swift
    func setLocalNotification() {
        let dateString = currentDateString()
        let notificationId = "Local-\(dateString.hashValue)"

        // 通知の内容を設定する
        let content = UNMutableNotificationContent()
        content.title = "Local"
        content.body = dateString
        content.userInfo = ["id": notificationId]

        // 通知の条件を設定する
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

        // 通知のリクエストを生成する
        let request = UNNotificationRequest(identifier: notificationId, content: content, trigger: trigger)
        // 通知をシステムに登録する
        UNUserNotificationCenter.current().add(request)
    }
```

今から三日後の午前１０時に通知させたい場合は、このようなコードになります。

```swift:NotificationManager.swift
    func setLocalNotification() {
        let dateString = currentDateString()
        let notificationId = "Local-\(dateString.hashValue)"

        // 通知の内容を設定する
        let content = UNMutableNotificationContent()
        content.title = "Local"
        content.body = dateString
        content.userInfo = ["id": notificationId]

        // 今から三日後の午前１０時を設定する
        let calender = Calendar.current
        var dateComponents = calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
        if let day = dateComponents.day {
            dateComponents.day = day + 3
        }
        dateComponents.hour = 10
        dateComponents.minute = 0
        dateComponents.second = 0

        // 通知の条件を設定する
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)

        // 通知のリクエストを生成する
        let request = UNNotificationRequest(identifier: notificationId, content: content, trigger: trigger)
        // 通知をシステムに登録する
        UNUserNotificationCenter.current().add(request)
    }
```

毎日、午後８時に通知させたい場合は、このようなコードになります。

```swift:NotificationManager.swift
    func setLocalNotification() {
        let dateString = currentDateString()
        let notificationId = "Local-\(dateString.hashValue)"

        // 通知の内容を設定する
        let content = UNMutableNotificationContent()
        content.title = "Local"
        content.body = dateString
        content.userInfo = ["id": notificationId]

        // 午後８時を設定する
        var dateComponents = DateComponents(hour: 20)
        // 通知の条件を設定する
        // 繰り返すので repeats を有効にする
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

        // 通知のリクエストを生成する
        let request = UNNotificationRequest(identifier: notificationId, content: content, trigger: trigger)
        // 通知をシステムに登録する
        UNUserNotificationCenter.current().add(request)
    }
```

# まとめ
通知処理の実装はコロコロ変わっているという印象があります。
次回は通知表示のカスタマイズについて整理したいと思います。

# 参考
http://dev.classmethod.jp/smartphone/iphone/user-notifications-framework-10/