April 9, 2017

iOS10で通知済みのローカル通知を削除する

Categories: 技術 | Tags: #Swift #UserNotifications #tips


iOS10から UserNotifications が加わったことにより、ローカル通知の扱いが少し変わっています。
今回はiOS10で通知済みのローカル通知(通知センターに表示されたもの)が、通知をタップせずに起動した場合にiOS9までと同じ方法では 消えない ことに気がついたのでそれをまとめておきます。
(※ちなみに通知センターから通知をタップして起動した場合は消えてくれます。)

iOS9まで

iOS9までは、通知センターの通知をタップして起動しなかった場合に、
以下の実装を起動後任意のタイミングで行うことで、通知前/通知後のローカル通知に関わらず削除することができました。

UIApplication.shared.cancelAllLocalNotifications()

iOS10から

iOS10からは、上記の方法では、通知前のローカル通知しか消えなくなっています。
(iOS10だとこのメソッドがdeprecatedになっています…。)

@available(iOS, introduced: 4.0, deprecated: 10.0, message: "Use UserNotifications Framework's -[UNUserNotificationCenter removeAllPendingNotificationRequests]")
open func cancelAllLocalNotifications()

iOS10で通知済みのものを削除する

UserNotifications frameworkでは、ローカル通知に関して通知前のものを pending 、 通知後のものを delivered として扱っているようです。
今回は、 delivered になった通知を削除したいので、以下のように UNUserNotificationCenter を使って削除処理を書きます。
今回は全てを削除するものと、identifierを見て削除するもの、userInfoを見て削除するものそれぞれのサンプルを出してみました。

let center = UNUserNotificationCenter.current()

// 全ての通知済みの通知を削除する
center.removeAllDeliveredNotifications()

// notification.requestのIDで絞って消す
center.getDeliveredNotifications { notifications in
    let identifiers = notifications
        .filter { $0.request.identifier == "foobar"}
        .map { $0.request.identifier }
    center.removeDeliveredNotifications(withIdentifiers: identifiers)
}

// notificationに設定したuserInfoで比較する場合
// notification.request.contentにuserInfoが渡るのでそれを使って削除する
center.getDeliveredNotifications { notifications in
    let identifiers = notifications
        .filter { notification in
            let userInfo = notification.request.content.userInfo
            return castOrNil(userInfo["identifier"]) == "foobar"
        }.map { $0.request.identifier }
    center.removeDeliveredNotifications(withIdentifiers: identifiers)
}

// Helper function
func castOrNil<T, U>(_ value: T?) -> U? {
    return value as? U
}

通知前のものは

一応 UIApplication.shared.cancelAllLocalNotifications() でもpendingなものは消えてくれますが、iOS10を対象にするとdeprecatedになるので、やはり同じようにこんな感じで処理を書いてあげます。

let center = UNUserNotificationCenter.current()

// 全てのpendingな通知を削除する
center.removeAllPendingNotificationRequests()

// requestのIDで絞って消す
center.getPendingNotificationRequests { requests in
    let identifiers = requests
        .filter { $0.identifier == "foobar"}
        .map { $0.identifier }
    center.removePendingNotificationRequests(withIdentifiers: identifiers)
}

// notificationに設定したuserInfoで比較する場合
// request.contentにuserInfoが渡るのでそれを使って削除する
center.getPendingNotificationRequests { requests in
    let identifiers = requests
        .filter { request in
            let userInfo = request.content.userInfo
            return castOrNil(userInfo["id"]) == "12345"
        }.map { $0.identifier }
    center.removePendingNotificationRequests(withIdentifiers: identifiers)
}

最後に

iOS10ではなるべくUserNotificationsを使うようにすれば変なところでハマらずに済みそうです。


written by sgr-ksmt