こんにちは、餅屋です!
今回は、SwiftUIでバックグラウンドのローカル通知を実装する方法をご紹介します。
ローカル通知とは、アプリがバックグラウンドの時も表示されるこういったものです。
見た目はプッシュ通知と変わりませんが、プッシュ通知がサーバー等外部のシステムを介して通知するのに対し、ローカル通知は外部を介さずアプリ自体が通知します。
なので、ローカル通知はオフラインでも通知することが可能です。
今回はサンプルアプリとして、「ボタンを押してバックグラウンドにすると、ローカル通知が発行されるだけアプリ」を実装します。
目次
見た目作成
まずはアプリの見た目を作成します。
見た目と言っても、ローカル通知を発行するボタンがあるだけです。
import SwiftUI
struct ContentView: View {
//ボタンに表示するテキストのプロパティ
@State var buttonText: String = "ローカル通知を発行する"
var body: some View {
//ローカル通知を出すボタン
Button(action: { }) {
//ボタンの見た目
Text(buttonText)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ボタンのテキストは、場合によってちょこっと表示を変えたいので変数にしておきます。
ボタンを押したときの処理(action)は、後ほど記述するので空白です。
UserNotificationsをインポート
通知関連の機能を使えるようにUserNotificationsをインポートします。
import SwiftUI
import UserNotifications//コレ
struct SwiftUIView_: View {
通知の表示許可をとるrequestAuthorization()メソッド
ローカル通知を表示させるためには、アプリを使用しているユーザーに対して通知する許可をとらなければなりません。
よくアプリの初回起動で表示される↓コレのこと。
今回はボタンを押したときに通知許可ウィンドウ表示させるので、ボタンのアクション内に、通知許諾ウィンドウを表示するUNUserNotificationCenter.current().requestAuthorization()メソッドを書きます。
//ローカル通知を出すボタン
Button(action: {
//通知許諾ウィンドウ表示(初回だけ)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound]){
(granted, _) in
if granted {
//通知が許可されているときの処理
}else {
//通知が拒否されているときの処理
}
}
}) {
//ボタンの見た目
Text(buttonText)
}
requestAuthorization()メソッドはapple側で
func requestAuthorization(options: UNAuthorizationOptions = [],
completionHandler: @escaping (Bool, Error?) -> Void)
と宣言されていて、
第一引数optionに許可項目配列、
第二引数completionHandlerに「①通知の可否のBool値」と「②エラー内容」を引数にした処理(クロージャ)をとります。
実行時、本来ならば省略.requestAuthorization(option:[ ], completionHandler: { })
のように第二引数も()内側に書きますが、
最後の引数としてクロージャが指定される場合、{ }を( )の外に出すことができます。後置記法といいます。
ですので、省略.requestAuthorization(option:[ ]) { }
のように書いています。
それぞれの引数について、詳しく見てみましょう。
第一引数 option:許可項目配列
第一引数のoption:[ ]の中には、許可が欲しい通知項目を入れます。
複数ある場合は「,」で区切ってください。
設定できる項目は色々ありますが、一般的なアプリで必要なものは下記の3つ。
- .alert → 通知の許可
- .sound → 通知の際に音を鳴らすことの許可
- .badge → 通知の際に、アプリのアイコンの右上に赤丸で数字を表示させることの許可
今回バッジ表示は使わないので、.alertと.soundだけにします。
第二引数 completionHandler:「①通知の可否のBool値」と「②エラー内容」を引数にとったクロージャ
第二引数はcompletionHandlerは「①通知の可否のBool値」と「②エラー内容」の2つを引数を使う処理(クロージャ)をとっています。
クロージャとは、{(引数A) in 引数Aを使った処理} のように書く処理のことです。
噛み砕いて言うと、
requestAuthorization()の第二引数completionHandlerに
{(①(通知の可否のBool値)につけたい変数名,②(エラー内容)につけたい変数名) in ①や②を使った処理}
を渡すと、変数①に通知の可否のBool値を取得、変数②にエラー内容を取得(無かったらnil)し、さらに「①と②の変数を使ってやりたい処理」を実行してくれる、
という感じですかね。
()の中に入れる変数名は、(granted, error)とかそれっぽいものをつけたり、(granted , _)みたいにアンダースコアで省略も可能です。
今回は、エラーを使わないので(granted, _)のように後者の変数を省略しました。
通知許諾ウィンドウが表示されるのはは最初の一回だけ
requestAuthorization()メソッドは初めて実行されたときだけ通知許諾ウィンドウを表示させ、2回目以降の実行時は通知許諾ウィンドウ無しに、引数completionHandlerの処理のみを遂行します。
上のコードではgranted変数に通知の可否が入るので、grantedのif文によって、
通知許諾ウィンドウで一度許可したら何度押しても「 //通知が許可されているときの処理
」が、
通知許諾ウィンドウで一度拒否したら、何度押しても「 //通知が拒否されているときの処理
」が、実行されます。
これはユーザーがiPhoneの設定で通知許可/拒否を切り替えるまで変わりません。
ただしアプリを再インストールした場合は、通知許諾ウィンドウが再度表示されるようになります。
それでは、//通知が許可されているときの処理、//通知が拒否されているときの処理 の中身をそれぞれ作っていきましょう。
通知が許可されているときの処理
通知が許可されているときの処理として、ローカル通知の内容やタイミングを指定し発行処理を実行します。
ただ行数が多くなるので、ContentView内にmakeNotification()メソッドを作成し、それをボタンアクションのif granted{}内で実行します。
import SwiftUI
import UserNotifications
struct ContentView: View {
@State var buttonText = "5秒後にローカル通知を発行する"
//①通知関係のメソッド作成
func makeNotification(){
//②通知タイミングを指定
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
//③通知コンテンツの作成
let content = UNMutableNotificationContent()
content.title = "ローカル通知"
content.body = "ローカル通知を発行しました"
content.sound = UNNotificationSound.default
//④通知タイミングと通知内容をまとめてリクエストを作成。
let request = UNNotificationRequest(identifier: "notification001", content: content, trigger: trigger)
//⑤④のリクエストの通りに通知を実行させる
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
var body: some View {
//ローカル通知を出すボタン
Button(action: {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]){
(granted, error) in
if granted {
//⑥メソッドを実行
makeNotification()
}else{
}
}
}) {
//ボタンの見た目
Text(buttonText)
}
}
}
①makeNotification()メソッドを作成します。
②発行するタイミングを指定するUNTimeIntervalNotificationTrigger
クラスのインスタンスを作成します。
通知発行を爆発としたら、爆弾の導火線の長さを決める作業です。
引数timeInterval
に、メソッド発動した何秒後に通知を出すかの数値(秒)、
引数repeats
には、通知を繰り返すか否かのbool値を入れます。
今回はバックグラウンドでのみ通知を出すので、バックグラウンドにするまでの時間も考慮して通知はボタンを押下してから5秒後に発動するようにします。
なのでtimeIntervalは5、通知は繰り返さなくていいので、repeatsはfalseにします。
ちなみに、repeats利用する場合、Intervalは60(秒)以上の値が必要だそうです。
③通知のコンテンツの内容を設定します。これは爆弾の中身にあたります。
UNMutableNotificationContentクラスのインスタンスを作成し、そのプロパティを設定していきます。
設定できるコンテンツはいろいろありますが、今回は一番シンプルに、title、body、soundを設定します。
titleとbodyは文字列を入れ、サウンドは content.sound = UNNotificationSound.default
とするとデフォルトの通知音(ティロリン♪)が鳴ります。
④設定したトリガーと内容をリクエストとしてまとめます。爆弾を一つに組み立てるイメージです。
⑤ UNNotificationRequest
クラスのインスタンスを作成し、通知のリクエスト要求して(爆弾の導火線に着火)、通知に関する処理は一通り完成です。
⑥作成したメソッドを、if granted()内で実行します。
通知が拒否されているときの処理
通知が拒否されているときは何度押しても通知は発行されないのですが、見た目が変わらないと発行されたかどうかわからず不親切です。
ですので、else{}部分に通知が拒否されているときの処理として、ボタンの文言を変えてあげます。
//ローカル通知を出すボタン
Button(action: {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]){
(granted, error) in
if granted {
makeNotification()
}else {
//①通知が拒否されているという
buttonText = "通知が拒否されているので発動できません"
//②1秒後に表示を元に戻しておく
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
buttonText = "5秒後にローカル通知を発行する"
}
}
}
}) {
//ボタンの見た目
Text(buttonText)
}
①で、通知が発動できない旨のテキストに変更します。
ただこれだけだとアプリを再起動しない限り、iPhoneの設定から通知許可してもこのテキストが表示されてしまいます。
ですので、②で1秒後に表示を戻すようにします。
一定時間後にしたい処理は
DispatchQueue.main.asyncAfter(deadline: .now() + 何秒後に実行したいかの秒数) { 処理 }
で書きます。
完成
完成しました。
すべてのコードです。
import SwiftUI
import UserNotifications
struct ContentView: View {
@State var buttonText = "5秒後にローカル通知を発行する"
//通知関係メソッド
func makeNotification(){
//通知タイミングを指定
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
//通知コンテンツの作成
let content = UNMutableNotificationContent()
content.title = "ローカル通知"
content.body = "ローカル通知です"
content.sound = UNNotificationSound.default
//通知リクエストを作成
let request = UNNotificationRequest(identifier: "notification001", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
var body: some View {
//ローカル通知発行ボタン
Button(action: {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]){
(granted, error) in
if granted {
//通知が許可されている場合の処理
makeNotification()
}else {
//通知が拒否されている場合の処理
//ボタンの表示を変える
buttonText = "通知が拒否されているので発動できません"
//1秒後に表示を戻す
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
buttonText = "5秒後にローカル通知を発行する"
}
}
}
}) {
//ボタンのテキストを表示
Text(buttonText)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
終わりに
今回は一番シンプルにローカル通知を実装しました。
ローカル通知は他にも、フォアグラウンド表示や指定時間の発行など奥深いので、機会があったらもう少し込み入ったことも書いてみようと思います!