iOSのnavigationControllerの戻るボタンコールバック


102

ビューをナビゲーションコントローラーにプッシュしました。戻るボタンを押すと、自動的に前のビューに戻ります。スタックからビューをポップする前に、戻るボタンが押されたときにいくつかのことをしたいと思います。戻るボタンのコールバック関数はどれですか?



この[ソリューション] [1]をチェックアウトしてください。[戻る]ボタンのスタイルも保持されています。[1]:stackoverflow.com/a/29943156/3839641
Sarasranglt

回答:


162

ウィリアム・ジョクシュの答えは、簡単なトリックでこの問題を解決します。

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

32
このコードは、ユーザーが戻るボタンをタップしたときに実行されるだけでなく、すべてのイベントでビューがポップされます(たとえば、右側に完了ボタンまたは保存ボタンがある場合)。
意味の重要性

7
または、新しいビューに進むとき。
GuybrushThreepwood 2013

これは、ユーザーが左端からパンしたときにも呼び出されます(interactivePopGestureRecognizer)。私の場合、ユーザーが左端からパンしていないときにユーザーが後ろに押したときを特に探しています。
カイルクレッグ14年

2
戻るボタンが原因だったわけではありません。たとえば、リラックスしたセグエになる可能性があります。
smileBot

1
私は疑問を持っています、なぜこれをviewDidDisappearで行わないのですか?
JohnVanDijk 2015

85

私の意見では、最良の解決策です。

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

しかし、それはiOS5 +でのみ機能します


3
この手法では、戻るボタンのタップと巻き戻しのセグエを区別できません。
smileBot

willMoveToParentViewControllerおよびviewWillDisappearメソッドは、コントローラーを破棄する必要があることを説明していません。didMoveToParentViewControllerが正しい
Hank

27

ユーザーの確認などのためにビューがポップされるにイベントを処理できるように、戻るボタンをオーバーライドすることをお勧めします。

viewDidLoadでUIBarButtonItemを作成し、sel。を渡してself.navigationItem.leftBarButtonItemをそれに設定します。

- (void) viewDidLoad
{
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”back
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];

self.navigationItem.leftBarButtonItem = backButton;
[backButton release];

}
- (void) handleBack:(id)sender
{
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];

}

次に、UIAlertViewを発生させてアクションを確認し、ビューコントローラをポップするなどの操作を実行できます。

または、新しい戻るボタンを作成する代わりに、UINavigationControllerデリゲートメソッドに準拠して、戻るボタンが押されたときにアクションを実行できます。


UINavigationControllerDelegateは、戻るボタンがタップされたときに呼び出されるメソッドがありません。
意味の問題

この手法により、ビューコントローラーのデータの検証と、ナビゲーションコントローラーの戻るボタンからの条件付き戻りが可能になります。
gjpc 2013

このソリューションは、iOS 7以降のエッジスワイプ機能を壊します
Liron Yahdav、2015

9

これはこれを検出する正しい方法です。

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        //do stuff

    }
}

このメソッドは、ビューがプッシュされたときにも呼び出されます。したがって、parent == nilをチェックすることは、スタックからビューコントローラをポップするためのものです。


9

私はこの解決策に終わります。戻るボタンをタップすると、viewDidDisappearメソッドが呼び出されます。trueを返すisMovingFromParentViewControllerセレクターを呼び出すことで確認できます。データを(デリゲートを使用して)戻すことができます。

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

    }
    if (self.isMovingFromParentViewController) {
       //moving back
        //pass to viewCollection delegate and update UI
        [self.delegateObject passBackSavedData:self.dataModel];

    }
}

お忘れなく[super viewDidDisappear:animated]
SamB

9

多分それは遅すぎるかもしれませんが、私は以前にも同じ動作を望んでいました。そして、私が行ったソリューションは、現在App Storeにあるアプリの1つで非常にうまく機能しています。誰も同じような方法で行くのを見たことがないので、ここでそれを共有したいと思います。このソリューションの欠点は、サブクラス化が必要になることUINavigationControllerです。メソッドスウィズリングを使用することでそれを回避できる可能性がありますが、それほど遠くまで行きませんでした。

したがって、デフォルトの戻るボタンは実際にはによって管理されUINavigationBarます。ユーザーが戻るボタンをタップしたら、を呼び出しUINavigationBarて上部をポップするかどうかをデリゲートに尋ねます。実際にこれを実装しますが、採用することを公に宣言しません(なぜ!?)。このイベントをインターセプトするには、のサブクラスを作成し、への適合を宣言して実装します。一番上のアイテムをポップするかどうかを返します。残っている場合は戻ります。UINavigationItemnavigationBar(_:shouldPop:)UINavigationControllerUINavigationBarDelegateUINavigationControllerUINavigationBarDelegatenavigationBar(_:shouldPop:)truefalse

2つの問題があります。1つ目は、ある時点でのUINavigationControllerバージョンを呼び出す必要があることですnavigationBar(_:shouldPop:)。しかし、それがへのUINavigationBarController適合を公に宣言していないためUINavigationBarDelegate、それを呼び出そうとすると、コンパイル時エラーが発生します。私が行った解決策は、Objective-Cランタイムを使用して実装を直接取得し、それを呼び出すことです。誰かがより良い解決策を持っているかどうか私に知らせてください。

他の問題は、ユーザーが戻るボタンをタップしたときにnavigationBar(_:shouldPop:)最初に呼び出されることですpopViewController(animated:)。を呼び出してビューコントローラをポップすると、順序が逆になりますpopViewController(animated:)。この場合、ブール値を使用して、popViewController(animated:)navigationBar(_:shouldPop:)に呼び出されたかどうかを検出します。これは、ユーザーが[戻る]ボタンをタップしたことを意味します。

また、UIViewControllerユーザーが[戻る]ボタンをタップした場合にナビゲーションコントローラーをポップするかどうかをナビゲーションコントローラーに尋ねるように拡張します。ビューコントローラーはfalse必要なアクションに戻って実行し、popViewController(animated:)後で呼び出すことができます。

class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
    // If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
    // If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
    private var didCallPopViewController = false

    override func popViewController(animated: Bool) -> UIViewController? {
        didCallPopViewController = true
        return super.popViewController(animated: animated)
    }

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
        if didCallPopViewController {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        }

        // The following code is called only when the user taps on the back button.

        guard let vc = topViewController, item == vc.navigationItem else {
            return false
        }

        if vc.shouldBePopped(self) {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        } else {
            return false
        }
    }

    func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
        didCallPopViewController = false
    }

    /// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
    /// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
    /// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
    private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
        let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
        typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
        let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
        return shouldPop(self, sel, navigationBar, item)
    }
}

extension UIViewController {
    @objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        return true
    }
}

そして、コントローラーを表示して、を実装しshouldBePopped(_:)ます。このメソッドを実装しない場合、デフォルトの動作は、ユーザーが通常どおりに戻るボタンをタップするとすぐにビューコントローラーをポップすることです。

class MyViewController: UIViewController {
    override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        let alert = UIAlertController(title: "Do you want to go back?",
                                      message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
            navigationController.popViewController(animated: true)
        }))
        present(alert, animated: true, completion: nil)
        return false
    }
}

ここで私のデモを見ることができます。

ここに画像の説明を入力してください


これは素晴らしいソリューションであり、ブログポストに焼き込む必要があります!今私が探しているものにはやり過ぎのようですが、他の状況では、これは確かに試す価値があります。
ASSeeger

6

「スタックからビューをポップする前に」の場合:

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}

5

viewControllersに問い合わせるよりも適切な方法があります。コントローラーを、戻るボタンのあるnavigationBarのデリゲートにすることができます。ここに例があります。戻るボタンの押下を処理するコントローラーの実装で、UINavigationBarDelegateプロトコルを実装することを伝えます。

@interface MyViewController () <UINavigationBarDelegate>

次に、初期化コードのどこか(おそらくviewDidLoad内)で、コントローラーをナビゲーションバーのデリゲートにします。

self.navigationController.navigationBar.delegate = self;

最後に、shouldPopItemメソッドを実装します。このメソッドは、戻るボタンが押されるとすぐに呼び出されます。スタックに複数のコントローラーまたはナビゲーションアイテムがある場合は、それらのナビゲーションアイテムのうちのどれがポップされているか(アイテムパラメーター)を確認して、必要なときにのみカスタムの操作を行うようにします。次に例を示します。

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}

4
私のためにうまくいきませんでした。"***キャッチされない例外 'NSInternalInconsistencyException'によりアプリを終了します、理由: 'コントローラーによって管理されるUINavigationBarに手動でデリゲートを設定することはできません。'"
DynamicDan

残念ながら、これはUINavigationControllerでは機能しません。代わりに、UINavigationBarを含む標準のUIViewControllerが必要です。これは、NavigationControllerが提供する自動ビューコントローラーのプッシュとポップのいくつかを利用できないことを意味します。ごめんなさい!
Carlos Guzman

NavigationBarControllerの代わりにUINavigationBarを使用したところ、問題なく動作しました。問題はNavigationBarControllerに関するものですが、このソリューションは無駄がありません。
2014年

3

「viewWillDisappear」または同様のメソッドを使用できない場合は、UINavigationControllerをサブクラス化してみてください。これはヘッダークラ​​スです:

#import <Foundation/Foundation.h>
@class MyViewController;

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

実装クラス:

#import "CCNavigationController.h"
#import "MyViewController.h"

@implementation CCNavigationController {

}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    @"This is the moment for you to do whatever you want"
    [self.viewController doCustomMethod];
    return [super popViewControllerAnimated:animated];
}

@end

一方、このviewControllerをカスタムNavigationControllerにリンクする必要があるため、通常のviewControllerのviewDidLoadメソッドで次のようにします。

@implementation MyViewController {
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        ((CCNavigationController*)self.navigationController).viewController = self;
    }
}

3

これは私が実装した別の方法です(巻き戻しセグエでテストしませんでしたが、このページの他のソリューションに関して他の人が述べているように、おそらく区別されません)、子VCがプッシュする前に親ビューコントローラーにアクションを実行させますビュースタックからポップされます(私はこれを元のUINavigationControllerから数レベル下に使用しました)。これは、childVCがプッシュされる前にアクションを実行するためにも使用できます。これには、カスタムのUIBarButtonItemまたはUIButtonを作成する代わりに、iOSシステムの戻るボタンを操作するという利点があります。

  1. 親VCにUINavigationControllerDelegateプロトコルを採用させ、デリゲートメッセージを登録します。

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. このUINavigationControllerDelegateインスタンスメソッドを実装しますMyParentViewController

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        // Test if operation is a pop; can also test for a push (i.e., do something before the ChildVC is pushed
        if (operation == UINavigationControllerOperationPop) {
            // Make sure it's the child class you're looking for
            if ([fromVC isKindOfClass:[ChildViewController class]]) {
                // Can handle logic here or send to another method; can also access all properties of child VC at this time
                return [self didPressBackButtonOnChildViewControllerVC:fromVC];
            }
        }
        // If you don't want to specify a nav controller transition
        return nil;
    }
  3. 上記のUINavigationControllerDelegateインスタンスメソッドで特定のコールバック関数を指定した場合

    -(id <UIViewControllerAnimatedTransitioning>)didPressBackButtonOnAddSearchRegionsVC:(UIViewController *)fromVC {
        ChildViewController *childVC = ChildViewController.new;
        childVC = (ChildViewController *)fromVC;
    
        // childVC.propertiesIWantToAccess go here
    
        // If you don't want to specify a nav controller transition
        return nil;

    }


1

これは私にとってSwiftで機能するものです:

override func viewWillDisappear(_ animated: Bool) {
    if self.navigationController?.viewControllers.index(of: self) == nil {
        // back button pressed or back gesture performed
    }

    super.viewWillDisappear(animated)
}

0

ストーリーボードを使用していて、プッシュセグエから来ている場合は、をオーバーライドすることもできますshouldPerformSegueWithIdentifier:sender:

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