iOS 9〜12 通知権限管理|Swiftで推播通知の状態確認と権限要求を最適化
iOS 9〜12の通知権限問題に直面する開発者向け。Swiftを使い、通知許可状態の正確な判定と権限要求を効率化し、ユーザー体験を向上させる実践的な解決策を紹介します。
本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。
記事一覧
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 オブジェクトを宣言する
1
2
3
4
5
6
enum NotificationStatusType {
case authorized // 許可済み
case denied // 拒否済み
case notDetermined // 未決定
}
var notificationStatus: Observable<NotificationStatusType?> = Observable(nil) // 通知状態を監視するオブジェクト
notificationStatus/NotificationStatusType の4つの状態はそれぞれ次の通り対応しています:
nil = オブジェクト初期化中…検出中…
notDetermined = ユーザーに通知の許可をまだ求めていない
authorized = ユーザーが通知の許可を求められ、「許可」を選択した状態
denied = ユーザーが通知の許可を求められ、「許可しない」を選択した状態
2. 通知権限の状態を検出する方法の構築:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 内に以下を追加しました:
1
2
3
4
5
6
//appdelegate.swift:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
// iOS 9以下(含)で通知許可のダイアログが表示され、許可または拒否を押すとこのメソッドが呼ばれます
UserDefaults.standard.set("iOS9NotificationIsDetermined", true)
checkNotificationPermissionStatus()
}
通知権限の状態オブジェクトや検出方法が構築された後、appDelegateにはさらに…
1
2
3
4
5
6
7
8
//appdelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
checkNotificationPermissionStatus() // 通知権限の状態を確認する
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
checkNotificationPermissionStatus() // 通知権限の状態を確認する
}
APPの起動時とバックグラウンドからの復帰時にプッシュ通知の状態を再度チェックする方法はどうすればいいですか?
以上が検出部分です。次に、未確認の場合に通知権限を要求する方法を見てみましょう。
3. 通知権限の要求:
1
2
3
4
5
6
7
8
9
10
11
12
13
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. アプリケーション(静的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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)”) )
(設定アプリの特定のページを開く)
メソッドでの遷移は、審査落ちします!審査落ちします!審査落ちします!(実体験)
これはPrivate APIです
5. アプリケーション(動的)
動的に状態を変更する部分について、notificationStatusオブジェクトはObservableを使用しているため、状態を常に監視する必要があるviewDidLoad内に監視処理を追加できます:
1
2
3
4
5
6
7
8
9
10
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から変換されたもの by ZMediumToMarkdown.
本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

{:target="_blank"}](/assets/fd7f92d52baa/1*_iVzlJLNQ7f0hO7IWxg1Zg.gif)