iOS 9からiOS 12までのプッシュ通知権限状態の処理(Swift)
iOS 9 〜 iOS 12 に対応した通知権限の状態管理と権限要求のソリューション
なにをする?
前回の記事「 何?iOS 12でユーザー承認なしにプッシュ通知を送れる(Swift) 」で紹介したプッシュ通知許可取得のフロー改善ですが、前回のMurmur部分での改善後に新たな要件が発生しました:

-
ユーザーが通知機能をオフにしている場合、特定の機能ページで通知をオンに設定するよう促すことができます。
-
設定画面に遷移後、通知のオン/オフ操作があった場合、アプリに戻った際に状態が更新されるようにすること
-
プッシュ通知の権限をまだ尋ねていない場合は権限を求め、尋ねたが許可されていない場合は警告を表示し、尋ねて許可されている場合は操作を続行できます。
-
iOS 9〜iOS 12 もサポートする必要がある
1〜3は問題ありませんが、iOS 10以降のフレームワークUserNotificationsを使えばほぼ解決できます。厄介なのは4番目のiOS 9のサポートです。iOS 9ではregisterUserNotificationSettingsという古い方法を使うため、対応が簡単ではありません。さあ、一歩ずつ進めていきましょう!
考え方と構成:
まず、通知権限の状態を保存するグローバルな notificationStatus オブジェクトを宣言し、処理が必要な画面でプロパティ監視を追加します(ここでは Observable を使ってプロパティ変化の購読を行っていますが、適したKVOやRx、ReactiveCocoaを使っても構いません)。
そして、appDelegate の didFinishLaunchingWithOptions(アプリ起動時)、applicationDidBecomeActive(バックグラウンドから復帰時)、didRegisterUserNotificationSettings(iOS 9 以下の通知許可処理)内で、プッシュ通知の許可状態を確認し notificationStatus の値を更新します。
処理が必要な画面はそれに応じて反応し(例:通知がオフになっている警告を表示)、対応した処理を行います。
1. まずはグローバルな notificationStatus オブジェクトを宣言する
enum NotificationStatusType {
case authorized // 許可済み
case denied // 拒否済み
case notDetermined // 未決定
}
var notificationStatus: Observable<NotificationStatusType?> = Observable(nil) // 通知ステータスの監視用
notificationStatus/NotificationStatusType の4つの状態はそれぞれ以下に対応しています:
-
nil = オブジェクト初期化中…検出中…
-
notDetermined = ユーザーに通知の許可をまだ求めていない状態
-
authorized = ユーザーに通知の許可を尋ね、「許可」を選択した状態
-
denied = ユーザーに通知の許可を尋ね、「許可しない」を選択した状態
2. 通知権限の状態を検出する方法の構築:
func checkNotificationPermissionStatus() {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
DispatchQueue.main.async {
// 注意!メインスレッドに戻す必要があります
if settings.authorizationStatus == .authorized {
// 許可済み
notificationStatus.value = NotificationStatusType.authorized
} else if settings.authorizationStatus == .denied {
// 拒否済み
notificationStatus.value = NotificationStatusType.denied
} else {
// 未確認
notificationStatus.value = NotificationStatusType.notDetermined
}
}
}
} else {
if UIApplication.shared.currentUserNotificationSettings?.types == [] {
if let iOS9NotificationIsDetermined = UserDefaults.standard.object(forKey: "iOS9NotificationIsDetermined") as? Bool,iOS9NotificationIsDetermined == true {
// 未確認
notificationStatus.value = NotificationStatusType.notDetermined
} else {
// 拒否済み
notificationStatus.value = NotificationStatusType.denied
}
} else {
// 許可済み
notificationStatus.value = NotificationStatusType.authorized
}
}
}
以上はまだ終わっていません!
目ざとい方は、≤ iOS 9の判定の中で「iOS9NotificationIsDetermined」というカスタムUserDefaultsを見つけたかもしれませんが、これは何のために使われているのでしょうか?
主な理由は、iOS 9以下のプッシュ通知権限の検出方法が、現在の権限が何かを取得するだけで判定しているためです。権限が空の場合は権限なしを意味しますが、まだ権限を尋ねていない場合も空になります。ここで問題が生じます。ユーザーがまだ尋ねられていないのか、それとも尋ねられて拒否したのかが分からないのです。
ここでは、判定用のカスタムUserDefaults「iOS9NotificationIsDetermined」を使用し、appDelegateのdidRegisterUserNotificationSettingsに以下を追加しました:
//appdelegate.swift:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
//iOS 9以下(含)で通知許可のダイアログが表示され、「許可」または「不許可」を押すとこのメソッドが呼ばれる
UserDefaults.standard.set("iOS9NotificationIsDetermined", true)
checkNotificationPermissionStatus()
}
通知権限の状態オブジェクトと検出方法が構築できたら、appDelegateにはさらに…
//appdelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
checkNotificationPermissionStatus() // 通知権限の状態を確認
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
checkNotificationPermissionStatus() // アプリがアクティブになったときに通知権限の状態を確認
}
APPの起動時とバックグラウンドからの復帰時にプッシュ通知の状態を再度確認する方法はどうすればよいか?
以上が検出部分です。次に、未許可の場合に通知権限を要求する方法を見ていきましょう。
3. 通知の許可を要求する:
func requestNotificationPermission() {
if #available(iOS 10.0, *) {
let permissiones:UNAuthorizationOptions = [.badge, .alert, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: permissiones) { (granted, error) in
DispatchQueue.main.async {
checkNotificationPermissionStatus()
}
}
} else {
application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))
// 前述のappdelegate.swiftのdidRegisterUserNotificationSettingsで後続のコールバックを処理します
}
}
検出と要求が完了しました。次に、どのように応用するか見てみましょう。
4. アプリケーション(静的)
if notificationStatus.value == NotificationStatusType.authorized {
//OK!
} else if notificationStatus.value == NotificationStatusType.denied {
//許可されていません
//ここではUIAlertControllerを表示し、タップで設定画面へ遷移する例です
let alertController = UIAlertController(
title: "親愛なるユーザー様、現在通知を受け取ることができません",
message: "結婚吧の通知権限をオンにしてください。",
preferredStyle: .alert)
let settingAction = UIAlertAction(
title: "設定へ移動",
style: .destructive,
handler: {
(action: UIAlertAction!) -> Void in
if let bundleID = Bundle.main.bundleIdentifier,let url = URL(string:UIApplicationOpenSettingsURLString + bundleID) {
UIApplication.shared.openURL(url)
}
})
let okAction = UIAlertAction(
title: "キャンセル",
style: .default,
handler: {
(action: UIAlertAction!) -> Void in
//まあ....
})
alertController.addAction(okAction)
alertController.addAction(settingAction)
self.present(alertController, animated: true) {
}
} else if notificationStatus.value == NotificationStatusType.notDetermined {
//未確認
requestNotificationPermission()
}
ご注意!!アプリの「設定」ページへ直接遷移するのは避けてください
UIApplication.shared.openURL(URL(string:”App-Prefs:root=(bundleID)”))
メソッドでの遷移は、審査拒否になります!審査拒否になります!審査拒否になります!(実体験)
これはプライベートAPIです
5. アプリケーション(動的)
動的に状態を変更する部分について、notificationStatusオブジェクトはObservableを使用しているため、状態を常に監視したいviewDidLoad内に監視処理を追加できます:
override func viewDidLoad() {
super.viewDidLoad()
notificationStatus.afterChange += { oldStatus,newStatus in
if newStatus == NotificationStatusType.authorized {
//print("❤️通知をオンにしてくれてありがとう")
} else if newStatus == NotificationStatusType.denied {
//print("😭うぅ…")
}
}
}
以上はあくまでサンプルコードであり、実際の使用やトリガーはご自身で調整してください。
*notificationStatus は Observable を使用するため、メモリ管理に注意してください。不要なときは解放し(メモリリーク防止)、必要なときは保持する(監視の無効化防止)必要があります。
最後に完成したデモを添付します:

私たちのプロジェクトはiOS 9からiOS 12までをサポートしており、iOS 8についてはテストを行っておらず、対応状況は不明です
Post MediumからZMediumToMarkdownによって変換されました。



コメント