[英]back button callback in navigationController in iOS
我已將視圖推到導航控制器上,當我按下后退按鈕時,它會自動轉到上一個視圖。 我想在按下后退按鈕之前做一些事情,然后再將視圖彈出堆棧。 哪個是后退按鈕回調功能?
William Jockusch的回答用簡單的技巧解決了這個問題。
-(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 +
覆蓋后退按鈕可能更好,這樣您就可以在彈出視圖之前處理事件,例如用戶確認。
在viewDidLoad中創建一個UIBarButtonItem並將self.navigationItem.leftBarButtonItem設置為傳遞給它的sel
- (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委托方法,以便在按下后退按鈕時執行操作。
我最終得到了這個解決方案。 當我們點擊后退按鈕viewDidDisappear方法調用時。 我們可以通過調用返回true的isMovingFromParentViewController選擇器來檢查。 我們可以傳回數據(使用Delegate).hope這可以幫助某人。
-(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是用於從堆棧彈出視圖控制器
對於“BEFORE彈出堆棧視圖”:
- (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方法。 按下后退按鈕時會調用此方法。 如果堆棧中有多個控制器或導航項,您可能想要檢查哪些導航項被彈出(item參數),以便您只在預期時執行自定義操作。 這是一個例子:
-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
NSLog(@"Back button got pressed!");
//if you return NO, the back button press is cancelled
return YES;
}
如果您不能使用“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;
}
}
這是我實現的另一種方式(沒有使用unwind segue測試它,但它可能不會像其他人在本頁中的其他解決方案中所說的那樣區分)讓父視圖控制器在子VC推送之前執行操作從視圖堆棧中彈出(我從原始的UINavigationController中使用了幾個級別)。 這也可以用於在推送childVC之前執行操作。 這具有使用iOS系統后退按鈕的附加優勢,而不必創建自定義UIBarButtonItem或UIButton。
讓您的父VC采用UINavigationControllerDelegate
協議並注冊委托消息:
MyParentViewController : UIViewController <UINavigationControllerDelegate> -(void)viewDidLoad { self.navigationcontroller.delegate = self; }
在MyParentViewController
實現此UINavigationControllerDelegate
實例方法:
- (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 (ie, 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;
}
也許這有點太晚了,但我之前也想要同樣的行為。 我使用的解決方案在App Store上的一個應用程序中運行良好。 由於我沒有看到任何人使用類似的方法,我想在這里分享。 這個解決方案的缺點是它需要子類化UINavigationController
。 雖然使用Method Swizzling可能有助於避免這種情況,但我沒有那么做。
因此,默認后退按鈕實際上由UINavigationBar
管理。 當用戶點擊后退按鈕時, UINavigationBar
詢問其委托是否應該通過調用navigationBar(_:shouldPop:)
來彈出頂部UINavigationItem
。 UINavigationController
實際上實現了這一點,但它沒有公開聲明它采用UINavigationBarDelegate
(為什么!?)。 要攔截此事件,請創建UINavigationController
的子類,聲明其與UINavigationBarDelegate
一致性並實現navigationBar(_:shouldPop:)
。 如果應彈出頂部項,則返回true
。 如果它應該停留,則返回false
。
有兩個問題。 首先,您必須在某個時刻調用navigationBar(_:shouldPop:)
的UINavigationController
版本。 但是UINavigationBarController
沒有公開聲明它與UINavigationBarDelegate
一致性,試圖調用它將導致編譯時錯誤。 我使用的解決方案是使用Objective-C運行時直接獲取實現並調用它。 如果有人有更好的解決方案,請告訴我。
另一個問題是如果用戶點擊后退按鈕,則首先調用navigationBar(_:shouldPop:)
,然后調用popViewController(animated:)
。 如果通過調用popViewController(animated:)
彈出視圖控制器,則順序會反轉。 在這種情況下,我使用布爾值來檢測是否在navigationBar(_:shouldPop:)
之前調用了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
}
}
你可以在這里查看我的演示。
這是它在Swift中對我有用的東西:
override func viewWillDisappear(_ animated: Bool) {
if self.navigationController?.viewControllers.index(of: self) == nil {
// back button pressed or back gesture performed
}
super.viewWillDisappear(animated)
}
如果您正在使用故事板並且您來自推送segue,您也可以覆蓋shouldPerformSegueWithIdentifier:sender:
.
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.