公開中のアプリ

[Swift/Xcode]初心者必見!iOSアプリ作成から公開まで徹底解説!#25「ローカル通知編②」

今回はローカル通知のコードを書いていきます〜!

結構悩みましたがなんとか実装できました!
こんな感じで実装します!

今回実機で画面収録したのでちょっとわかりにくいかもです。

動作環境は下記の通りです!
Xcode Version 12.5 Swift version 5.4

実装の流れ

まずは実装の流れを把握しておきましょう!

①メニュー画面から遷移時にユーザーに通知許可の確認をする

[許可の場合]
②そのまま遷移
③アラーム設定画面でローカル通知を実装

[拒否の場合]
④遷移させない
⑤再びメニュー画面から「貯金忘れ防止アラーム」をタップした場合、
 365bankアプリの設定画面へ誘導して通知をONにしてもらう。

通知を許可しらもう

ローカル通知を表示させるには、必ずユーザーに許可してもらう必要があります。

誰もが経験ある事だとは思いますがこんな感じで聞かれます。
英語になっちゃってますが(^^;

まずはここを実装していきましょう!
コードはコピペすればOKなので簡単です!

問題はどこで表示させるかだと思います!

実はこの表示は一回しか出せません(^^;
(アプリを削除すればまた出せます!)

色んなアプリを見てると、最初に起動させた時に表示されることが多いと思うんですけど、僕の場合、後から許可できるので許可しない事が多いです(^^;

なので、最初に表示させるよりはアラームを使おうと思ってアラーム設定画面を開く時に表示させてあげるのが個人的には良いのかなって思っています!

今回のアプリで言うとメニュー画面の「貯金忘れ防止アラーム」をタップした時に表示させようと思います。

通知許可の確認アラートを出すには下記コードを書くだけでOKです!

MenuViewController.swiftのswitch文「アラート設定画面へ」の中に書いていきます!

コード全部は最後に載せますのでそちらも参考にして下さい!

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
    //[.alert, .badge, .sound]と指定されているので、「アラート、バッジ、サウンド」の3つに対しての許可をリクエストした
    if granted {
        print("許可")
        
    } else {
        print("許可しない")
    }
}

このコードを元に「許可する場合」と「許可しない場合」で処理を分けていきます!

まずは許可する場合です。

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
    //[.alert, .badge, .sound]と指定されているので、「アラート、バッジ、サウンド」の3つに対しての許可をリクエストした
    if granted {
        print("許可")
        //アラーム設定画面へ遷移(メインスレッドで処理)
        DispatchQueue.main.async {
            let NotificationViewController = NotificationViewController.init()
            let NV = UINavigationController.init(rootViewController: NotificationViewController)
            self.present(NV, animated: true, completion: nil)
        }
        
    } else {
        print("許可しない")
    }
}

許可する場合はアラーム設定画面へ遷移させるだけでOKですね!
メインスレッドで処理しないとエラーになってしまうので6行目のメインスレッドの中で遷移させています!


次は許可しない場合ですが、ちょっと一工夫しないといけません。

許可しないボタンを押すと何も処理はせずアラートを閉じます。

ですがこのままでは、再びメニュー画面から「貯金忘れ防止アラーム」をタップした場合、何も処理されないままです。

なので2回目以降は「通知が許可されていません」とアラートを出します。

そのアラートでは、「通知設定画面へボタン」と「キャンセルボタン」を配置します。

キャンセルの場合は何もせずアラートを閉じます。

通知設定画面へを押すと365アプリの設定画面へ遷移させる実装をしたいと思います!

case 0:
print("アラーム設定画面へ")
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
//[.alert, .badge, .sound]と指定されているので、「アラート、バッジ、サウンド」の3つに対しての許可をリクエストした
if granted {
    //「許可」が押された場合
    //アラーム設定画面へ遷移(メインスレッド)
    DispatchQueue.main.async {
        let NotificationViewController = NotificationViewController.init()
        let NV = UINavigationController.init(rootViewController: NotificationViewController)
        self.present(NV, animated: true, completion: nil)
    }
    //UserDefaultsにtrueを保持させる
    UserDefaults.standard.set(true, forKey: "permit")

} else {
    //「許可しない」が押された場合
    if UserDefaults.standard.bool(forKey: "permit") == true {
    //通知を許可しない状態で2回目以降「貯金忘れ防止アラーム」をタップした時にアラートを出す
    DispatchQueue.main.async {
        let alertController = DOAlertController(title: "通知設定が許可されていません", message: "", preferredStyle: .alert)
        let cancelAction = DOAlertAction(title: "Cancel", style: .cancel, handler: nil)
        let okAction = DOAlertAction(title: "設定画面へ", style: .default) { action in
            //OKが押された時、365bankの設定画面へ遷移
            guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
              return
            }
            if UIApplication.shared.canOpenURL(settingsUrl)  {
              if #available(iOS 10.0, *) {
                UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
                })
              }
              else  {
                UIApplication.shared.openURL(settingsUrl)
              }
            }
        }
        alertController.alertViewBgColor = UIColor.white
        alertController.alertView.layer.cornerRadius = 15
        alertController.titleFont = UIFont(name: "HiraMaruProN-W4", size: 16)
        alertController.titleTextColor = UIColor(hex: self.common.text, alpha: 1)
        alertController.buttonFont[.cancel] = UIFont(name: "HiraMaruProN-W4", size: 20)
        alertController.buttonBgColor[.cancel] = UIColor(hex: self.common.main, alpha: 1)
        alertController.buttonBgColorHighlighted[.cancel] = UIColor(hex: self.common.main, alpha: 1)
        alertController.buttonBgColor[.default] =  UIColor(hex: self.common.sub, alpha: 1)
        alertController.buttonBgColorHighlighted[.default] =  UIColor(hex: self.common.sub, alpha: 1)
        alertController.addAction(cancelAction)
        alertController.addAction(okAction)
        self.present(alertController, animated: true, completion: nil)
    }
} else {
    //通知を許可しないを押して一回目の処理
    UserDefaults.standard.set(true, forKey: "permit")
}
}
}

2回目以降の処理を判定するのに、UserDefaultsを使っています!

UserDefaultsは簡単なデータをスマホに保存する事ができる機能です!

今回はUserDefaultsを使ってbool型を保存して、trueの場合は2回目以降と言う判定をさせています!

ちなみにUserDefaultsはアプリを削除するとデータも削除されます!

今回は許可を押した場合と、拒否した場合の一回目の時にbool型をtrueにしています!

UserDefaultsの詳しい使い方はググればすぐ出てくるので興味のある方は調べてみて下さい!

365bankの設定画面に遷移させているのが、25行目からです!

コピペで簡単に遷移させる事ができるので便利ですね!

まとめ

ちょっと長くなっちゃったのでここまでにしておきます!

次回こそローカル通知実装します!!

編集したコード全部

MenuViewController.swift

import UIKit
import RealmSwift
import StoreKit

class MenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    

//Commonクラスのインスタンス生成
let common = Common()
//Cancelボタン
var cancelBtn: UIBarButtonItem!
//テーブルビューの変数
var tableView: UITableView?
//セル用の配列
let items = ["貯金忘れ防止アラーム", "使い方", "シェア", "評価", "データリセット"]
let icons = ["menuIcon-1", "menuIcon-2", "menuIcon-3", "menuIcon-4", "menuIcon-5"]
//Realm通知
var token : NotificationToken?

override func viewDidLoad() {
    super.viewDidLoad()
    
//Realmのデータが更新されたら検知する
let realm = try! Realm()
token = realm.observe({ (notification:Realm.Notification, realm) in
  print("realm is updated")
    //アラートの表示
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        let alertController = self.common.originalAlert(title: "削除しました", message: "")
        self.present(alertController, animated: true, completion: nil)
    }
})

//ナビゲーションコントローラーまわり
//タイトル
self.title = "メニュー"
//背景色
self.navigationController?.navigationBar.barTintColor = UIColor(hex: self.common.main , alpha: 1)
//セーフエリアとの境目の線を消す
self.navigationController?.navigationBar.shadowImage = UIImage()
//フォントの設定
self.navigationController?.navigationBar.titleTextAttributes
    = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont(name: "HiraMaruProN-W4", size: 20)!]
//Cancelボタンを追加
cancelBtn = UIBarButtonItem(title: "Cancel", style: .done, target: self, action: #selector(self.cancelBtnTapped))
cancelBtn.tintColor = UIColor(hex: self.common.white , alpha: 1)
self.navigationItem.leftBarButtonItem = cancelBtn
    
//テーブルビューの設定
self.tableView = {
    let tableView = UITableView(frame: self.view.bounds, style: .plain)
    tableView.autoresizingMask = [
      .flexibleWidth,
      .flexibleHeight
    ]

    tableView.delegate = self
    tableView.dataSource = self

    self.view.addSubview(tableView)

    tableView.register(MenuTableViewCell.self, forCellReuseIdentifier: "Cell")
    
    return tableView

  }()
}

//Cancelボタンのメソッド
@objc func cancelBtnTapped() {
    //前の画面に戻る
    dismiss(animated: true, completion: nil)
}

    
//テーブルビューまわり
//テーブルビューのセクションの数を設定
func numberOfSections(in tableView: UITableView) -> Int {
  return 1
}
//テーブルビューのセルの数を設定
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  //配列itemsの数になるように設定
  return self.items.count
}

//セルの設定
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //カスタムをセルを使えるように設定
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! MenuTableViewCell
    //ラベルのテキストとアイコン画像のファイル名を設定
    cell.setCell(item: self.items[indexPath.row], iconName: icons[indexPath.row])
    //セルの右側に<を付ける
    cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
    //セル選択時に背景色を変更しない
    cell.selectionStyle = UITableViewCell.SelectionStyle.none
    return cell
}
    
//セルを選択した時の設定
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.row {

case 0:
print("アラーム設定画面へ")
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
//[.alert, .badge, .sound]と指定されているので、「アラート、バッジ、サウンド」の3つに対しての許可をリクエストした
if granted {
    //「許可」が押された場合
    //アラーム設定画面へ遷移(メインスレッド)
    DispatchQueue.main.async {
        let NotificationViewController = NotificationViewController.init()
        let NV = UINavigationController.init(rootViewController: NotificationViewController)
        self.present(NV, animated: true, completion: nil)
    }
    //UserDefaultsにtrueを保持させる
    UserDefaults.standard.set(true, forKey: "permit")

} else {
    //「許可しない」が押された場合
    if UserDefaults.standard.bool(forKey: "permit") == true {
    //通知を許可しない状態で2回目以降「貯金忘れ防止アラーム」をタップした時にアラートを出す
    DispatchQueue.main.async {
        let alertController = DOAlertController(title: "通知設定が許可されていません", message: "", preferredStyle: .alert)
        let cancelAction = DOAlertAction(title: "Cancel", style: .cancel, handler: nil)
        let okAction = DOAlertAction(title: "設定画面へ", style: .default) { action in
            //OKが押された時、365bankの設定画面へ遷移
            guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
              return
            }
            if UIApplication.shared.canOpenURL(settingsUrl)  {
              if #available(iOS 10.0, *) {
                UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
                })
              }
              else  {
                UIApplication.shared.openURL(settingsUrl)
              }
            }
        }
        alertController.alertViewBgColor = UIColor.white
        alertController.alertView.layer.cornerRadius = 15
        alertController.titleFont = UIFont(name: "HiraMaruProN-W4", size: 16)
        alertController.titleTextColor = UIColor(hex: self.common.text, alpha: 1)
        alertController.buttonFont[.cancel] = UIFont(name: "HiraMaruProN-W4", size: 20)
        alertController.buttonBgColor[.cancel] = UIColor(hex: self.common.main, alpha: 1)
        alertController.buttonBgColorHighlighted[.cancel] = UIColor(hex: self.common.main, alpha: 1)
        alertController.buttonBgColor[.default] =  UIColor(hex: self.common.sub, alpha: 1)
        alertController.buttonBgColorHighlighted[.default] =  UIColor(hex: self.common.sub, alpha: 1)
        alertController.addAction(cancelAction)
        alertController.addAction(okAction)
        self.present(alertController, animated: true, completion: nil)
    }
} else {
    //通知を許可しないを押して一回目の処理
    UserDefaults.standard.set(true, forKey: "permit")
}
}
}

case 1:
    print("ウォークスルー画面へ")
case 2:
    print("シェア")
    share()
case 3:
    print("評価")
    //レビューダイアログを表示
    if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
        SKStoreReviewController.requestReview(in: scene)
    }
case 4:
    print("データリセット")
    deleteAlert()
default:
    break
    }
}

func share() {
    //URLは自分のアプリのappstoreURLを設定する(今回はもしも貯金箱のURL)
    let shareUrl = ShareItem(URL(string: "https://apps.apple.com/jp/app/%E3%82%82%E3%81%97%E3%82%82%E8%B2%AF%E9%87%91%E7%AE%B1/id1553026256")!)
    let activityVC = UIActivityViewController(activityItems: [shareUrl], applicationActivities: nil)
    //ipadの場合の処理
    if UIDevice.current.userInterfaceIdiom == .pad {
        let screenSize = UIScreen.main.bounds
        activityVC.popoverPresentationController?.sourceView = self.view
        activityVC.popoverPresentationController?.sourceRect = CGRect(x:screenSize.size.width/2, y: screenSize.size.height-200, width: 0, height: 0)
    }
    present(activityVC, animated: true, completion: nil)
}

func deleteAlert() {
    //タイトルとメッセージの設定
    let alertController = DOAlertController(title: "本当に削除しますか?", message: "削除すると元には戻せません!", preferredStyle: .alert)
    //キャンセルボタンの設定
    let cancelAction = DOAlertAction(title: "Cancel", style: .cancel, handler: nil)
    //OKボタンの設定
    let okAction = DOAlertAction(title: "OK", style: .default) { action in
        //メインビューコントローラー取得
        let nc = self.presentingViewController as! UINavigationController
        let vcNum = nc.viewControllers.count
        let firstVC = nc.viewControllers[vcNum - 1] as! MainViewController
        //先に色とか変更しちゃう
        firstVC.deleteAlertBack()
        //realmDBを全て削除
        let realm = try! Realm()
        try! realm.write {
          realm.deleteAll()
        }
    }

    // アラートビューの背景色
    alertController.alertViewBgColor = UIColor.white
    alertController.alertView.layer.cornerRadius = 15
    // タイトルのフォント、文字色
    alertController.titleFont = UIFont(name: "HiraMaruProN-W4", size: 17)
    alertController.titleTextColor = UIColor(hex: "8a6b52", alpha: 1)
    alertController.messageFont = UIFont(name: "HiraMaruProN-W4", size: 17)
    alertController.messageTextColor = UIColor.red
    //キャンセルボタンの設定
    alertController.buttonFont[.cancel] = UIFont(name: "HiraMaruProN-W4", size: 17)
    alertController.buttonBgColor[.cancel] = UIColor(hex: common.main, alpha: 1)
    alertController.buttonBgColorHighlighted[.cancel] = UIColor(hex: common.main, alpha: 1)
    //OKボタンの設定
    alertController.buttonFont[.default] = UIFont(name: "HiraMaruProN-W4", size: 17)
    alertController.buttonBgColor[.default] = UIColor.red
    alertController.buttonBgColorHighlighted[.default] = UIColor.red

    // アラートコントローラにアクションを追加
    alertController.addAction(cancelAction)
    alertController.addAction(okAction)

    // 表示
    present(alertController, animated: true, completion: nil)
}

}

class ShareItem<T>: NSObject, UIActivityItemSource {

    private let item: T

    init(_ item: T) {
        self.item = item
    }

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return item
    }

    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return activityViewControllerPlaceholderItem(activityViewController)
    }
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

アプリ