あたも技術ブログ

セットジャパンコーポレーションの社員が運営しています。

【iOS 10】User Notifications Frameworkで通知をカスタムしてみる

iOS 10が公開されて1ヶ月が経ちました。

新しいAPIが大量に追加され、Swiftもメジャーアップデートし、色んな意味で楽しい開発ができています。

そして、発売日からちょうど1ヶ月後の、10月16日に予約開始日に予約していたiPhone 7 Plus ジェットブラックが届きました。(被写界深度エフェクトタノチーwwwww)

街なかの広告を見てイライラする日々からついに開放されました。

さて、今回はiOS 10で追加されたUser Notifications Frameworkを紹介します。

User Notifications Framework

これまでiOSでの通知は"Remote Notification"と"Local Notification"が別物として存在していましたが、User Notifications Frameworkではこの2つを統合し、同じフレームワーク内で処理することができます。

そして、アプリ起動中でも通知を表示できるようになったので、別途アプリ内通知を実装する必要がなくなりました。

発生条件

User Notifications Frameworkでは以下の4つのトリガーによって通知を発生します。

・Push

iOS 9までに存在していたRemote Notificationと同等

・Time Interval

一定時間後に通知を発生する

繰り返し可能だが、最短時間が60秒に制限される

・Calendar

日時指定で通知を発生する

DateComponentsに一致すれば繰り返し可能

・Location

位置情報に基づき通知を発生する

カスタム

地図やカレンダーの情報を通知上に表示したり、画像や動画、音声などのメディアを添付することができるようになりました。

Notification Content Extensionを実装することで自由にレイアウトを変更することができます。 通知直後は縮小表示になるので3DTouchに対応している場合は押し込むことで、対応していない場合は下に引き下げることで全体を表示します。

また、キーボードからの入力も行えるのでメッセンジャーアプリなどでアプリを起動することなく返信することができるようになります。

サンプル

今回は、Pushを除いたトリガーでの通知発生と画像の添付、アクションの追加までのサンプルを作成してみました。

まずは、通知を許可するようにユーザに求めるダイアログを表示します。

AppDelegateを以下のように編集します

import UserNotifications

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // 通知の許可をリクエスト
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { (granted, error) in
            if error != nil {
                return
            }
            
            if granted {
                debugPrint("通知許可")
                let center = UNUserNotificationCenter.current()
                center.delegate = self
            } else {
                debugPrint("通知拒否")
            }
        })
        
        return true
    }
}

次に通知を行いたい場所で以下の処理を行います。

トリガーの設定

5秒後に通知する場合

let trigger: UNNotificationTrigger
trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

60秒毎通知する場合

let trigger: UNNotificationTrigger
trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)

日時指定で通知する場合 (現在時間から1分後に設定)

let trigger: UNNotificationTrigger
var dateComponents = DateComponents()
dateComponents.minute = dateComponents.minute! + 1
trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)

位置情報によって通知する場合

let trigger: UNNotificationTrigger
let coordinate = CLLocationCoordinate2DMake(35.170915, 136.8793482)
let region = CLCircularRegion(center: coordinate, radius: 100.0, identifier: "description")
trigger = UNLocationNotificationTrigger(region: region, repeats: false)

表示の設定

通常

let content = UNMutableNotificationContent()
content.title = "タイトル"
content.body = "ボディ"
content.sound = UNNotificationSound.default()
    

// 通常
let request = UNNotificationRequest(identifier: "normal",
                                    content: content,
                                    trigger: trigger)
    

UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

画像を表示

let content = UNMutableNotificationContent()
content.title = "タイトル"
content.body = "ボディ"
content.sound = UNNotificationSound.default()
    
if let url = Bundle.main.url(forResource: "image", withExtension: "png") {
    let attachment = try? UNNotificationAttachment(identifier: "attachment", url: url, options: nil)
    if let attachment = attachment {
        content.attachments = [attachment]
    }
    // categoryIdentifierを設定
    content.categoryIdentifier = "attachment"
}
let request = UNNotificationRequest(identifier: "attachment",
                                    content: content,
                                    trigger: trigger)
    
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

アクション

UNNotificationCategoryに設定している"repry"と"cancel"はハンドリングの際に処理を分けるために使用します。

let content = UNMutableNotificationContent()
content.title = "タイトル"
content.body = "ボディ"
content.sound = UNNotificationSound.default()
    
let repry = UNNotificationAction(identifier: NotificationActionID.repry.rawValue,
                                 title: "返信", options: [])
    
let cancel = UNNotificationAction(identifier: NotificationActionID.cancel.rawValue,
                                  title: "キャンセル",
                                  options: [])
    
let category = UNNotificationCategory(identifier: "message",
                                      actions: [repry, cancel],
                                      intentIdentifiers: [],
                                      options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])

// categoryIdentifierを設定
content.categoryIdentifier = "message"
    
let request = UNNotificationRequest(identifier: "message",
                                    content: content,
                                    trigger: trigger)
    
//  通知をセット
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

通知のハンドリング

通知を受け取る際はAppdelegateへUNUserNotificationCenterDelegateを実装します

    /// 通知表示前のデリゲート
    ///
    /// - parameter center:            NotificationCenter
    /// - parameter notification:      Notification
    /// - parameter completionHandler: Handler
    private func userNotificationCenter(_ center: UNUserNotificationCenter,
                                        willPresent notification: UNNotification,
                                        withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
        let identifier = notification.request.identifier
        switch identifier {
        case "alert":
            completionHandler([.alert]) // 通知のみ行う
        case "both":
            completionHandler([.sound, .alert]) // サウンドと通知 
        default:
              ()
        }
    }



通知設定時のアクションに対応する動作を行う場合は以下のようにします。

アクションに対してNotificationActionIDを設定し、デリゲート内で分岐します。

public enum NotificationActionID: String {
    case repry
    case cancel
}
    
    /// 通知開封時のデリゲート
    ///
    /// - parameter center:            NotificationCenter
    /// - parameter response:          Notification
    /// - parameter completionHandler: Handler
    private func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: () -> Void) {
        
        switch response.actionIdentifier {
        case NotificationActionID.repry.rawValue:
            /* 返信処理 */
        case NotificationActionID.cancel.rawValue:
            /* キャンセル処理 */
        default:
            ()
        }
        
        debugPrint("opened")
        completionHandler()
    }