ビューをNavigation Controllerにプッシュしました。戻るボタンを押すと、自動的に前のビューに移動します。スタックからビューをポップする前に戻るボタンが押されたときにいくつかのことをしたいと思います。戻るボタンのコールバック関数はどれですか?
William Jockusch's answer この問題を簡単なトリックで解決します。
-(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];
}
私の意見では最善の解決策です。
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
しかし、それはiOS5 +でのみ動作します
イベントを処理できるように戻るボタンをオーバーライドすることをお勧めしますbeforeユーザーの確認などのためにビューがポップされます。
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を呼び出してアクションを確認し、View Controllerをポップするなどの操作を実行できます。
または、新しい戻るボタンを作成する代わりに、UINavigationControllerデリゲートメソッドに準拠して、戻るボタンが押されたときにアクションを実行できます。
私はこの解決策になります。戻るボタンをタップすると、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];
}
}
これは、これを検出する正しい方法です。
- (void)willMoveToParentViewController:(UIViewController *)parent{
if (parent == nil){
//do stuff
}
}
このメソッドは、ビューがプッシュされるときにも呼び出されます。したがって、parent == nilのチェックは、スタックからView Controllerをポップするためのものです
「スタックからビューをポップする前」:
- (void)willMoveToParentViewController:(UIViewController *)parent{
if (parent == nil){
NSLog(@"do whatever you want here");
}
}
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;
}
たぶん少し遅すぎるかもしれませんが、私は以前にも同じ振る舞いを望んでいました。私が行ったソリューションは、現在App Storeにあるアプリの1つで非常にうまく機能します。私は誰も同じような方法で行くのを見たことがないので、ここでそれを共有したいと思います。このソリューションの欠点は、UINavigationController
をサブクラス化する必要があることです。 Method Swizzling を使用すると、それを回避できるかもしれませんが、私はそこまで行きませんでした。
したがって、デフォルトの戻るボタンは、実際にはUINavigationBar
によって管理されます。ユーザーが戻るボタンをタップすると、UINavigationBar
は、デリゲートにnavigationBar(_:shouldPop:)
を呼び出して、一番上のUINavigationItem
をポップするかどうかを尋ねます。 UINavigationController
は実際にこれを実装しますが、UINavigationBarDelegate
を採用することを公に宣言していません(なぜ!?)。このイベントをインターセプトするには、UINavigationController
のサブクラスを作成し、UINavigationBarDelegate
への準拠を宣言し、navigationBar(_:shouldPop:)
を実装します。一番上の項目をポップする必要がある場合は、true
を返します。残る場合はfalse
を返します。
2つの問題があります。 1つは、ある時点でnavigationBar(_:shouldPop:)
のUINavigationController
バージョンを呼び出す必要があることです。ただし、UINavigationBarController
は、UINavigationBarDelegate
への準拠を公式に宣言していません。これを呼び出すと、コンパイル時エラーが発生します。私が行った解決策は、Objective-Cランタイムを使用して実装を直接取得し、呼び出すことです。誰かがより良い解決策を持っているかどうか教えてください。
もう1つの問題は、ユーザーが戻るボタンをタップすると、navigationBar(_:shouldPop:)
が最初に呼び出され、その後にpopViewController(animated:)
が呼び出されることです。 popViewController(animated:)
を呼び出してView Controllerがポップされると、順序は逆になります。この場合、popViewController(animated:)
の前にnavigationBar(_:shouldPop:)
が呼び出されたかどうかをブール値を使用して検出します。これは、ユーザーが[戻る]ボタンをタップしたことを意味します。
また、UIViewController
の拡張機能を作成して、ユーザーが戻るボタンをタップしたときにポップアップする必要があるかどうかをNavigation ControllerがView Controllerに問い合わせるようにします。 View Controllerは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(_:)
を実装します。このメソッドを実装しない場合、デフォルトの動作では、ユーザーが通常のように戻るボタンをタップするとすぐにView Controllerがポップされます。
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
}
}
あなたは私のデモを見ることができます こちら 。
これは、親View Controllerが子VCプッシュすると、ビュースタックからポップされます(元のUINavigationControllerから数レベル下にこれを使用しました)。これは、childVCがプッシュされる前にアクションを実行するためにも使用できます。これには、カスタムUIBarButtonItemまたはUIButtonを作成する代わりに、iOSシステムの戻るボタンを操作するという追加の利点があります。
親VCにUINavigationControllerDelegate
プロトコルを採用させ、デリゲートメッセージに登録させます:
MyParentViewController : UIViewController <UINavigationControllerDelegate>
-(void)viewDidLoad {
self.navigationcontroller.delegate = self;
}
この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;
}
上記の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;
}
「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;
}
}
これはSwiftで私にとってうまくいくものです:
override func viewWillDisappear(_ animated: Bool) {
if self.navigationController?.viewControllers.index(of: self) == nil {
// back button pressed or back gesture performed
}
super.viewWillDisappear(animated)
}
ストーリーボードを使用していて、プッシュセグエから来ている場合は、shouldPerformSegueWithIdentifier:sender:
をオーバーライドすることもできます。