Navbarで[戻る]ボタン(前の画面に戻る、親ビューに戻る)ボタンが押されたときにいくつかのアクションを実行する必要があります。
画面が消える前にイベントをキャッチし、データを一時停止して保存するアクションを起動するために実装できる方法はありますか?
UPDATE:一部のコメントによると、元の回答のソリューションは、iOS 8以降の特定のシナリオでは機能しないようです。詳細がなければ、実際にそうであることを確認することはできません。
ただし、そのような状況では、代替手段があります。 willMove(toParentViewController:)
をオーバーライドすることにより、View Controllerがポップされていることを検出できます。基本的な考え方は、parent
がnil
のときにView Controllerがポップされるということです。
詳細については、 "Container View Controllerの実装" をご覧ください。
IOS 5以降、この状況に対処する最も簡単な方法は、新しいメソッド- (BOOL)isMovingFromParentViewController
を使用することです。
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
は、ナビゲーションスタックでコントローラーをプッシュおよびポップするときに意味があります。
ただし、モーダルビューコントローラを提示する場合は、代わりに- (BOOL)isBeingDismissed
を使用する必要があります。
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
この質問 で述べたように、両方のプロパティを組み合わせることができます。
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
他のソリューションは、UINavigationBar
の存在に依存しています。その代わり、イベントをトリガーしたアクションから実行するために必要なタスクを切り離す、つまり戻るボタンを押すため、私のアプローチがより好きです。
viewWillAppear()
とviewDidDisappear()
は戻るボタンがタップされたときに呼び出されますが、他のときにも呼び出されます。詳細については、回答の終わりを参照してください。
willMoveToParentViewController(_:)
VC didMoveToParentViewController()
の助けを借りて、ORがその親(NavigationController)から削除されると、戻るボタンの検出がより適切に行われます。
親がnilの場合、View ControllerはNavigation Stackからポップされて閉じられます。 parentがnilでない場合、スタックに追加されて表示されます。
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController:parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
willMove
をdidMove
に交換し、self.parentをチェックして、作業を行うafterView Controllerが閉じられます。
何らかの非同期保存を行う必要がある場合、親をチェックしても遷移を「一時停止」することはできません。これを行うには、次を実装できます。ここでの欠点は、おしゃれなiOSスタイル/アニメーションの戻るボタンを失うことだけです。また、インタラクティブなスワイプジェスチャーにも注意してください。このケースを処理するには、次を使用します。
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
viewWillAppear
viewDidDisappear
の問題を取得できなかった場合、例を見てみましょう。 3つのView Controllerがあるとします。
detailVC
からlistVC
に戻り、settingsVC
に戻るときに、listVC
の呼び出しをたどりましょう
リスト>詳細(Push detailVC)Detail.viewDidAppear
<-が表示されます
詳細>設定(Push settingsVC)Detail.viewDidDisappear
<-消えます
そして戻ると...
設定>詳細(pop settingsVC)Detail.viewDidAppear
<-が表示されます
詳細>リスト(pop detailVC)Detail.viewDidDisappear
<-消えます
viewDidDisappear
は、戻るときだけでなく、進むときにも複数回呼び出されることに注意してください。迅速な操作が必要な場合がありますが、保存するネットワークコールなどのより複雑な操作の場合は、そうでない場合があります。
最初の方法
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
2番目の方法
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
私はこの問題で2日間プレイ(またはファイティング)しました。 IMOの最良のアプローチは、次のように拡張クラスとプロトコルを作成することです。
@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
@end
@interface UINavigationController(BackButtonHandler)
@end
@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = @selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
@end
これは、View ControllerがポップされるたびにUINavigationController
がnavigationBar:shouldPopItem:
への呼び出しを受け取るためです。そこで、戻るボタンが押されたかどうかを検出します(他のボタン)。あなたがしなければならない唯一のことは、戻るボタンが押されたView Controllerでプロトコルを実装することです。
すべてが問題ない場合は、backButtonPressedSel
内にView Controllerを手動でポップすることを忘れないでください。
既にUINavigationViewController
をサブクラス化してnavigationBar:shouldPopItem:
を実装している場合、心配する必要はありません。これは干渉しません。
バックジェスチャを無効にすることもできます。
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
これは、Swiftを使用したiOS 9.3.xで機能します。
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
ここの他のソリューションとは異なり、これは予期せずトリガーされるようには見えません。
これが機能しないと主張する人は間違っています:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
それはうまくいきます。それでは、広がらないという神話の原因は何ですか?
この問題は、differentメソッドの実装の誤り、つまりwillMove(toParent:)
の実装がsuper
を呼び出すのを忘れたことに起因するようです。
super
を呼び出さずにwillMove(toParent:)
を実装すると、self.isMovingFromParent
はfalse
になり、viewWillDisappear
の使用は失敗したように見えます。失敗しませんでした。あなたはそれを壊した。
記録のために、これは彼が探していたものに近いと思います…
UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];
self.navigationItem.leftBarButtonItem = l_backButton;
- (void) backToRootView:(id)sender {
// Perform some custom code
[self.navigationController popToRootViewControllerAnimated:YES];
}
purrrminator
が言うように、elitalon
による答えは完全には正しくありません。なぜなら、your stuff
はコントローラーをプログラムでポップしても実行されるからです。
私がこれまでに見つけた解決策はあまりいいものではありませんが、私にとってはうまくいきます。 elitalon
が言ったことに加えて、プログラムでポップしているかどうかも確認します。
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
そのプロパティをコントローラーに追加し、プログラムでポップする前にYESに設定する必要があります。
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
ご協力いただきありがとうございます!
最良の方法は、UINavigationControllerデリゲートメソッドを使用することです
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
これを使用して、UINavigationControllerを表示しているコントローラーを知ることができます。
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}
UINavigationControllerを使用するSwiftの場合:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
Coli88が言ったように、UINavigationBarDelegateプロトコルを確認する必要があります。
より一般的な方法では、- (void)viewWillDisapear:(BOOL)animated
を使用して、現在表示されているView Controllerによって保持されているビューが消えようとしているときにカスタム作業を実行することもできます。残念ながら、これはプッシュとポップの両方のケースをカバーします。
UIControlを左側のnavigationBarに追加することで、この問題を解決しました。
UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];
また、ビューが消えるときは、忘れずに削除する必要があります。
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
それで全部です!
INavigationBarDelegate Protocol を確認する必要があります。この場合、navigationBar:shouldPopItem:メソッドを使用できます。
7ynk3r の答えは、私が最後に使用したものに本当に近かったが、いくつかの微調整が必要だった。
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:@selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}
次のように、戻るボタンコールバックを使用できます。
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
Swiftバージョンの場合は、グローバルスコープのようにできます
extension UIViewController {
@objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}
以下では、戻るボタンのアクションを制御したいViewControllerに配置します:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}
self.navigationController.isMovingFromParentViewControllerは、使用しているiOS8および9では動作していません。
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}