はじめに
UINavigationController の左上に表示される「戻る」ボタンのイベントを取得したいことがたまにあります!
「戻る」ボタンのイベントを取得するために試行錯誤した結果です。
結論から言うとちゃんとイベントを取得したいなら navigationItem.leftBarButtonItem をカスタムしましょう![]()
サンプル
サンプルとして FirstiViewController(赤) -> SecondViewController(青) -> ThirdViewController(緑) と push で遷移する画面構成で SecondViewController の「戻る」について考えます。
SecondViewController からは Full Screen の modal 遷移(黄)もつけています。
deinit を使う
SecondViewController から戻る際は deinit が呼ばれるのでここで試してみます。
deinit {
print("戻る!!!")
}
SecondViewController から FirstViewController に戻る際に呼ばれますがここだと ThirdViewController から popToRootViewController などで FirstViewController に戻った場合と区別することが困難です![]()
UINavigationControllerDelegate を使う
UINavigationController には UINavigationControllerDelegate がありこのデリゲートを使ってイベント取得を試みます。
SecondViewController から戻った場合は FirstiViewController に戻るのでデリゲートで表示されるのが FirstiViewController かどうかで下記のように判定します。
extension SecondViewController: UINavigationControllerDelegate {
// ナビゲーション遷移の直前に呼ばれる
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController is FirstViewController {
print("戻る!!!")
}
}
// ナビゲーションの遷移直後に呼ばれる
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewController is FirstViewController {
print("戻る!!!")
}
}
}
willShow の方は ThirdViewController から popToRootViewController などで FirstViewController に戻った際も呼ばれてしまうので使うなら didShow の方になるでしょう。
「戻る」押下時にログ表示とかならできますがアラートを表示したいなど遷移を止めたいときなどは残念ながらこれではダメです![]()
(あと個人的に UINavigationControllerDelegate を各画面で使いたくない
)
viewDidDisappear を使う
viewWillDisappear と viewDidDisappear を使って試してみます。
こちらは willShow, didShow と同じようなタイミングで呼ばれます(SecondViewController が非表示になるタイミングです)。
気をつけないといけないのは modal 遷移の Full Screen などでも呼ばれることです。
SecondViewController から ThirdViewController に遷移する場合と modal 遷移の Full Screen などで他の画面に遷移する場合を判定しないといけないので下記のようにします。
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if navigationController?.viewControllers.contains(self) == false {
print("戻る")
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if navigationController == nil {
print("戻る")
}
}
didShow と同じで「戻る」押下時にログ表示とかならできますがアラートを表示したいなど遷移を止めたいときなどは残念ながらこれではダメです![]()
leftBarButtonItem をカスタムする
上に書いたやつはすべて厳密に言うと「戻る」ボタンを押下したかどうかは取れていません![]()
スワイプで戻るでも呼ばれるし、別にボタンを置いて popViewController しても呼ばれてしまいます。それに遷移途中でイベントを取っているので遷移自体を止めることはできません(アラート出してキャンセルとかできない)。
結局のところ leftBarButtonItem をカスタムしてボタンの押下イベントを取るしかありません!!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(back(_:)), for: .touchUpInside)
button.setTitle("Back", for: .normal)
button.setImage(UIImage(named: "back"), for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
button.imageEdgeInsets = .init(top: 0, left: -8, bottom: 0, right: 0)
navigationItem.leftBarButtonItem = .init(customView: button)
}
@objc private func back(_ sender: Any) {
print("戻る!!!")
navigationController?.popViewController(animated: true)
}
見た目も限りなく戻るボタン![]()
使った画像はこれです(てきとーに作りました)。
これで「戻る」ボタン押下イベントを完璧に取得できました![]()
がしかし、 leftBarButtonItem をカスタムするとスワイプバックできないし iOS14 からできたロングタップのメニュー表示もできなくなります![]()
結論
厳密に「戻る」ボタン押下イベントを取得したいなら leftBarButtonItem のカスタムかなという感じです。
(わたしの場合は戻るときにログとりたいという要件だったので viewDidDisappear を使いました。)
おわりに
「戻る」ボタン押下イベントの取得方法としては結局、 leftBarButtonItem のカスタムに落ち着きましたがそもそも戻るときにアラート出したいとかの場合は本当に push でいいのか遷移方法を見直すべきな気もします![]()
