はじめに
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 でいいのか遷移方法を見直すべきな気もします