ナビゲーションバーで「戻る」ボタンが押されたときの検出


135

ナビゲーションバーで[戻る]ボタン(前の画面に戻る、親ビューに戻る)ボタンが押されたときに、いくつかのアクションを実行する必要があります。

イベントをキャッチし、画面が消える前に一時停止してデータを保存するためのアクションを実行するために実装できるメソッドはありますか?




私はこのようにして、ここで決定を示しました
Taras

回答:


316

更新:一部のコメントによると、元の回答のソリューションはiOS 8以降の特定のシナリオでは機能しないようです。これ以上の詳細がなければ、実際にそうであることを確認することはできません。

あなたのそれらのためにしかし、そのような状況では代替案があります。オーバーライドすることで、ビューコントローラがいつポップされているかを検出できますwillMove(toParentViewController:)。基本的な考え方は、の場合、ビューコントローラーがポップされるということparentですnil

チェックアウト「コンテナビューコントローラの実装」詳細については。


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ます。代わりに、私のアプローチがより好きです。これは、実行に必要なタスクを、イベントをトリガーしたアクション(つまり、戻るボタンを押す)から切り離すためです。


私はあなたが答えるのが好きです。しかし、なぜ「self.isBeingDismissed」を使用したのですか?私の場合、「self.isBeingDismissed」のステートメントは実装されていません。
Rutvij Kotecha 2013

3
self.isMovingFromParentViewControllerプログラムを使用してナビゲーションスタックをポップしているとき、TRUEの値がありpopToRootViewControllerAnimatedます。私はあなたの答えに反対票を投じるべきですか?(件名は「戻る」ボタンがnavbarで押されたと言います)
kas-kad

2
素晴らしい答え、どうもありがとうございました。Swiftで使用したもの:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
Camillo Visini、2015年

1
あなただけの中にこれを行う必要があり-viewDidDisappear:、それはあなたが買ってあげることが可能ですので、-viewWillDisappear:することなく、-viewDidDisappear:あなたはナビゲーションコントローラアイテムを却下し、そのスワイプをキャンセルするためにスワイプ起動したときのように(。
ヒース国境

3
信頼できるソリューションではなくなったように見えます。私がこれを最初に使用したときに働いていました(iOS 10でした)。しかし今、私は誤ってそれが静かに動作を停止したことに気づきました(iOS 11)。「willMove(toParentViewController)」ソリューションに切り替える必要がありました。
Vitalii 2018年

100

一方でviewWillAppear()viewDidDisappear() されている戻るボタンをタップしたときに呼び出さ、それらはまた、他の時点で呼ばれています。詳細については、回答の最後を参照してください。

UIViewController.parentの使用

willMoveToParentViewController(_:)ORを使用してVCがその親(NavigationController)から削除されている場合、戻るボタンの検出はより適切に行われます。didMoveToParentViewController()

親がnilの場合、ビューコントローラーはナビゲーションスタックからポップされ、閉じられます。親が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(toParent parent: UIViewController?) {
    super.willMove(toParent: parent)
    if parent == nil {
        // The back button was pressed or interactive gesture used
    }
}

ビューコントローラーが閉じられた後に動作するようにスワップアウトwillMovedidMoveてself.parentを確認します。

却下をやめる

何らかの非同期保存を行う必要がある場合は、親をチェックしても遷移を「一時停止」できないことに注意してください。これを行うには、以下を実装します。ここでの欠点は、派手な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があるとします。

  1. ListVC:事物のテーブルビュー
  2. DetailVC:Thingの詳細
  3. SettingsVC:Thingのいくつかのオプション

上のコールを追跡することができますdetailVCあなたがから行くようlistVCsettingsVCと背中にlistVC

一覧>詳細(プッシュdetailVC)Detail.viewDidAppear< -現れ
詳細>設定(プッシュsettingsVCを)Detail.viewDidDisappear< -消えます

そして戻ると...
設定>詳細(ポップ設定 VC)<- 詳細をDetail.viewDidAppear表示
>リスト(ポップ詳細 VC)Detail.viewDidDisappear<-非表示

viewDidDisappear戻るときだけでなく、進むときにも何度も呼び出されることに注意してください。必要な場合がある迅速な操作の場合は、保存するためのネットワーク呼び出しのようなより複雑な操作の場合は、そうでない場合があります。


注:didMoveToParantViewController:ビューが表示されなくなったときに作業を行うユーザー。InteractiveGesutreでiOS7に役立つ
WCByrne

didMoveToParentViewController *タイプミスがあります
thewormsterror '

[super willMoveToParentViewController:parent]を呼び出すことを忘れないでください!
ScottyB 2014年

2
親ビューコントローラーにポップしている場合、親パラメーターはnilであり、このメソッドが表示されるビューが表示されている場合は非nilです。その事実を使用して、[戻る]ボタンが押されたときのみアクションを実行でき、ビューに到達したときはアクションを実行できません。結局のところ、それが最初の質問でした。:)
Mike

1
これは、プログラムでを使用するときにも呼び出される_ = self.navigationController?.popViewController(animated: true)ため、[戻る]ボタンを押すだけでは呼び出されません。[ 戻る]が押されたときにのみ機能する通話を探しています。
イーサンアレン

16

最初の方法

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

第二の方法

-(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];
}

1
2番目の方法は私のために働いた唯一の方法でした。最初の方法は、私の見解が提示されたときにも呼び出されましたが、これは私のユースケースでは受け入れられませんでした。
marcshilling 2015年

10

これが機能しないと主張する人は間違っています:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isMovingFromParent {
        print("we are being popped")
    }
}

それはうまくいきます。それでは、それが広まっていない神話を広めているのは何ですか?

問題は別のメソッドの誤った実装、つまりのwillMove(toParent:)呼び出しがを呼び出すのを忘れたことに起因するようsuperです。

あなたが実装する場合はwillMove(toParent:)呼び出さずsuper、その後self.isMovingFromParentになるfalseとの使用がviewWillDisappear失敗しているように見えます。失敗しませんでした。あなたはそれを壊しました。

注:実際の問題は通常、最初のビューコントローラーがポップされたことを2番目のビューコントローラーが検出することです。こちらのより一般的な説明も参照してください:統一されたUIViewControllerが「最前線になった」検出?

編集コメントは、これがでviewDidDisappearはなくであるべきだと示唆していviewWillDisappearます。


このコードは、戻るボタンがタップされたときに実行されますが、VCがプログラムによってポップされた場合にも実行されます。
biomiker

@biomikerもちろんですが、それは他のアプローチにも当てはまります。ポッピングはポッピングです。問題は、プログラムでポップしなかったときにポップを検出する方法です。プログラムでポップすると、ポップしていることがすでにわかっているため、検出するものはありません。
マット

はい、これは他のアプローチのいくつかに当てはまり、それらの多くは同様のコメントを持っています。これは特定の反論を伴う最近の回答であり、それを読んだときに私の期待は高まっていたので、はっきりさせておきました。ただし、記録のために、問題は「戻る」ボタンの押下を検出する方法です。戻るボタンが押されているかどうかを示さずに、戻るボタンが押されていない状況でも実行されるコードは、おそらく質問がもっと多かったとしても、実際の質問を完全に解決しないと言うのは合理的な議論ですその点で明示的。
biomiker

1
残念ながら、これはtrueインタラクティブなスワイプポップジェスチャー(ビューコントローラーの左端から)に戻ります。スワイプで完全にポップしなかった場合でも同様です。そのwillDisappearため、でチェックインする代わりに、作業でチェックインしdidDisappearます。
バダンガネーシュ

1
@badhanganeshありがとう、その情報を含めるように回答を編集しました。
マット

9

私はこの問題で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がポップUINavigationControllernavigationBar:shouldPopItem:れるたびにへの呼び出しを受け取るため、機能します。そこで、戻るボタンが押されたかどうかが検出されます(他のボタン)。あなたがしなければならない唯一のことは、戻るが押されたビューコントローラにプロトコルを実装することです。

backButtonPressedSelすべて問題がなければ、内部でビューコントローラを手動でポップすることを忘れないでください。

すでにサブクラス化UINavigationViewControllerして実装navigationBar:shouldPopItem:している場合でも、心配する必要はありません。

戻るジェスチャーを無効にすることもできます。

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

1
この答えはほぼ完成しましたが、2つのビューコントローラーが頻繁にポップされることがわかりました。YESを返すと、呼び出し元のメソッドがpopを呼び出すため、popを​​呼び出すと、2つのビューコントローラーがポップされます。:もっとdeetsための別の質問にこの回答(もっとupvotesに値する非常に良い答え)を参照してくださいstackoverflow.com/a/26084150/978083
ジェイソン・リッジ

良い点、私の説明はその事実について明確ではなかった。「すべてに問題がない場合は、手動でビューコントローラをポップすることを忘れないでください」これは、「NO」を返す場合のみです。それ以外の場合、フローは通常のポップです。
7ynk3r 2015

1
「else」ブランチの場合、自分でpopを処理したくない場合はスーパー実装を呼び出し、正しいと思われるものは何でも返すようにすることをお勧めします。 。
ベン・シンクレア

9

これは、Swiftを使用するiOS 9.3.xで私にとっては機能します。

override func didMoveToParentViewController(parent: UIViewController?) {
    super.didMoveToParentViewController(parent)

    if parent == self.navigationController?.parentViewController {
        print("Back tapped")
    }
}

ここの他のソリューションとは異なり、これは予期せずトリガーされるようには見えません。


代わりにwillMoveを使用することをお勧めします
ユージーン

4

記録として、これは彼が探していたもののほうが多いと思います…

    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];
    }

1
Paulのおかげで、このソリューションは非常に簡単です。残念ながら、アイコンは異なります。これは「巻き戻し」アイコンであり、戻るアイコンではありません。たぶん、戻るアイコンを使用する方法があるかもしれません...
Ferran Maylinch '11年

2

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];

ご協力いただきありがとうございます!


2

最良の方法は、UINavigationControllerデリゲートメソッドを使用することです

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated

これを使用して、どのコントローラーがUINavigationControllerを表示しているかを知ることができます。

if ([viewController isKindOfClass:[HomeController class]]) {
    NSLog(@"Show home controller");
}

これは正解としてマークする必要があります!また、人々に思い出させるために、もう1行追加したいかもしれません-> self.navigationController.delegate = self;
Mike Critchley

2

この問題を解決するには、左側のナビゲーションバーにUIControlを追加します。

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];
    }    
}

それで全部です!


2

次のように、戻るボタンのコールバックを使用できます。

- (BOOL) navigationShouldPopOnBackButton
{
    [self backAction];
    return NO;
}

- (void) backAction {
    // your code goes here
    // show confirmation alert, for example
    // ...
}

迅速なバージョンでは、グローバルスコープのようなものを行うことができます

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
    }
}

その下で、戻るボタンのアクションを制御するビューコントローラに配置します。

override func navigationShouldPopOnBackButton() -> Bool {
    self.backAction()//Your action you want to perform.

    return true
}

1
誰かが反対票を投じた理由がわかりません。これは断然最良の答えのようです。
Avinash

@AvinashどこnavigationShouldPopOnBackButtonから来たの?パブリックAPIの一部ではありません。
elitalon

@elitalonすみません、これは半分の答えでした。残りのコンテキストが問題であると思っていました。とにかく答えを今すぐ更新しました
アビナッシュ

1

Coli88が言ったように、UINavigationBarDelegateプロトコルを確認する必要があります。

より一般的な方法では- (void)viewWillDisapear:(BOOL)animated、現在表示されているビューコントローラーによって保持されているビューが消えようとしているときに、を使用してカスタム作業を実行することもできます。残念ながら、これはプッシュとポップのケースをカバーするでしょう。


1

UINavigationControllerを使用したSwiftの場合:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}

1

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;
    }
}


0

self.navigationController.isMovingFromParentViewControllerは、私が使用するiOS8および9では動作しなくなりました。

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.navigationController.topViewController != self)
    {
        // Is Popping
    }
}

-1

(迅速)

最終的に見つかったソリューション..私たちが探していたメソッドは、UINavigationControllerのデリゲートメソッドである "willShowViewController"です。

//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        //set delegate to current class (self)
        navigationController?.delegate = self
    }

    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
        //MyViewController shoud be the name of your parent Class
        if var myViewController = viewController as? MyViewController {
            //YOUR STUFF
        }
    }
}

1
このアプローチの問題点は、と結合MyViewControllerすることPushedControllerです。
clozach
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.