ios 在导航控制器中设置后退按钮的动作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1214965/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:13:45  来源:igfitidea点击:

Setting action for back button in navigation controller

iosiphonecocoa-touchuinavigationcontrolleruibarbuttonitem

提问by Parrots

I'm trying to overwrite the default action of the back button in a navigation controller. I've provided a target an action on the custom button. The odd thing is when assigning it though the backbutton attribute it doesn't pay attention to them and it just pops the current view and goes back to the root:

我正在尝试覆盖导航控制器中后退按钮的默认操作。我在自定义按钮上提供了一个目标操作。奇怪的是在分配它时虽然 backbutton 属性它不注意它们,它只是弹出当前视图并返回到根:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

As soon as I set it through the leftBarButtonItemon the navigationItemit calls my action, however then the button looks like a plain round one instead of the arrowed back one:

一旦我通过它设置它leftBarButtonItemnavigationItem它就会调用我的操作,但是按钮看起来像一个普通的圆形而不是带箭头的背面:

self.navigationItem.leftBarButtonItem = backButton;

How can I get it to call my custom action before going back to the root view? Is there a way to overwrite the default back action, or is there a method that is always called when leaving a view (viewDidUnloaddoesn't do that)?

我怎样才能让它在返回根视图之前调用我的自定义操作?有没有办法覆盖默认的后退动作,或者是否有一种在离开视图时始终调用的方法(viewDidUnload不这样做)?

回答by William Jockusch

Try putting this into the view controller where you want to detect the press:

尝试将其放入要检测新闻的视图控制器中:

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

回答by onegray

I've implemented UIViewController-BackButtonHandlerextension. It does not need to subclass anything, just put it into your project and override navigationShouldPopOnBackButtonmethod in UIViewControllerclass:

我已经实现了UIViewController-BackButtonHandler扩展。它不需要子类化任何东西,只需将其放入您的项目并覆盖类中的navigationShouldPopOnBackButton方法UIViewController

-(BOOL) navigationShouldPopOnBackButton {
    if(needsShowConfirmation) {
        // Show confirmation alert
        // ...
        return NO; // Ignore 'Back' button this time
    }
    return YES; // Process 'Back' button click and Pop view controler
}

Download sample app.

下载示例应用程序

回答by HansPinckaers

Unlike Amagrammer said, it's possible. You have to subclass your navigationController. I explained everything here(including example code).

与 Amagrammer 所说的不同,这是可能的。你必须子类化你的navigationController. 我在这里解释了一切(包括示例代码)。

回答by kgaidis

Swift Version:

迅捷版:

(of https://stackoverflow.com/a/19132881/826435)

https://stackoverflow.com/a/19132881/826435

In your view controller you just conform to a protocol and perform whatever action you need:

在您的视图控制器中,您只需遵守协议并执行您需要的任何操作:

extension MyViewController: NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool {
        performSomeActionOnThePressOfABackButton()
        return false
    }
}

Then create a class, say NavigationController+BackButton, and just copy-paste the code below:

然后创建一个类,比如说NavigationController+BackButton,复制粘贴下面的代码:

protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool
}

extension UINavigationController {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // Prevents from a synchronization issue of popping too many navigation items
        // and not enough view controllers or viceversa from unusual tapping
        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped
        var shouldPop = true
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            shouldPop = viewController.shouldPopOnBackButtonPress()
        }

        if (shouldPop) {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            // Prevent the back button from staying in an disabled state
            for view in navigationBar.subviews {
                if view.alpha < 1.0 {
                    UIView.animate(withDuration: 0.25, animations: {
                        view.alpha = 1.0
                    })
                }
            }

        }

        return false
    }
}

回答by psycho

For some threading reasons, the solution mentionned by @HansPinckaers wasn't right for me, but I found a very easier way to catch a touch on the back button, and I wanna pin this down here in case this could avoid hours of deceptions for someone else. The trick is really easy : just add a transparent UIButton as a subview to your UINavigationBar, and set your selectors for him as if it was the real button! Here's an example using Monotouch and C#, but the translation to objective-c shouldn't be too hard to find.

由于某些线程原因,@HansPinckaers 提到的解决方案不适合我,但我找到了一种非常简单的方法来触摸后退按钮,我想把它固定在这里,以防万一这可以避免数小时的欺骗其他人。诀窍很简单:只需添加一个透明的 UIButton 作为 UINavigationBar 的子视图,并为他设置选择器,就好像它是真正的按钮一样!下面是一个使用 Monotouch 和 C# 的示例,但到 Objective-c 的翻译应该不难找到。

public class Test : UIViewController {
    public override void ViewDidLoad() {
        UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
        b.BackgroundColor = UIColor.Clear; //making the background invisible
        b.Title = string.Empty; // and no need to write anything
        b.TouchDown += delegate {
            Console.WriteLine("caught!");
            if (true) // check what you want here
                NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
        };
        NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
    }
}

Fun fact : for testing purposes and to find good dimensions for my fake button, I set its background color to blue... And it shows behindthe back button! Anyway, it still catches any touch targetting the original button.

有趣的事实:为了测试目的并为我的假按钮找到合适的尺寸,我将它的背景颜色设置为蓝色......它显示后退按钮后面!无论如何,它仍然可以捕获针对原始按钮的任何触摸。

回答by Alex Reynolds

It isn't possible to do directly. There are a couple alternatives:

直接做是不可能的。有几种选择:

  1. Create your own custom UIBarButtonItemthat validates on tap and pops if the test passes
  2. Validate the form field contents using a UITextFielddelegate method, such as -textFieldShouldReturn:, which is called after the Returnor Donebutton is pressed on the keyboard
  1. 创建您自己的自定义UIBarButtonItem,如果测试通过,则在点击和弹出时进行验证
  2. 使用UITextField委托方法验证表单字段内容,例如在键盘上按下或按钮-textFieldShouldReturn:后调用的方法ReturnDone

The downside of the first option is that the left-pointing-arrow style of the back button cannot be accessed from a custom bar button. So you have to use an image or go with a regular style button.

第一个选项的缺点是无法从自定义栏按钮访问后退按钮的向左箭头样式。因此,您必须使用图像或使用常规样式按钮。

The second option is nice because you get the text field back in the delegate method, so you can target your validation logic to the specific text field sent to the delegate call-back method.

第二个选项很好,因为您可以在委托方法中返回文本字段,因此您可以将验证逻辑定位到发送到委托回调方法的特定文本字段。

回答by Jason Moore

This technique allows you to change the text of the "back" button without affecting the title of any of the view controllers or seeing the back button text change during the animation.

这种技术允许您更改“后退”按钮的文本,而不会影响任何视图控制器的标题或在动画过程中看到后退按钮文本的变化。

Add this to the init method in the callingview controller:

将此添加到调用视图控制器的 init 方法中:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];

回答by Jawad Zeb

Easiest way

最简单的方法

You can use the UINavigationController's delegate methods. The method willShowViewControlleris called when the back button of your VC is pressed.do whatever you want when back btn pressed

您可以使用 UINavigationController 的委托方法。willShowViewController当按下 VC 的后退按钮时调用该方法。按下后退按钮时做任何你想做的事

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

回答by Sarasranglt

Found a solution which retains the back button style as well. Add the following method to your view controller.

找到了一个保留后退按钮样式的解决方案。将以下方法添加到您的视图控制器。

-(void) overrideBack{

    UIButton *transparentButton = [[UIButton alloc] init];
    [transparentButton setFrame:CGRectMake(0,0, 50, 40)];
    [transparentButton setBackgroundColor:[UIColor clearColor]];
    [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.navigationController.navigationBar addSubview:transparentButton];


}

Now provide a functionality as needed in the following method:

现在根据需要在以下方法中提供功能:

-(void)backAction:(UIBarButtonItem *)sender {
    //Your functionality
}

All it does is to cover the back button with a transparent button ;)

它所做的只是用透明按钮覆盖后退按钮;)

回答by brahimm

Overriding navigationBar(_ navigationBar:shouldPop): This is nota good idea, even if it works. for me it generated random crashes on navigating back. I advise you to just override the back button by removing the default backButton from navigationItem and creating a custom back button like below:

Overriding navigationBar(_ navigationBar:shouldPop):这不是一个好主意,即使它有效。对我来说,它在导航回来时产生了随机崩溃。我建议您通过从导航项中删除默认的 backButton 并创建一个自定义后退按钮来覆盖后退按钮,如下所示:

override func viewDidLoad(){
   super.viewDidLoad()

   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...

}

========================================

========================================

Building on previous responses with UIAlertin Swift5in a Asynchronousway

异步方式在Swift5中使用UIAlert 的先前响应构建


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped

        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {

            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}


On your controller

在您的控制器上


extension MyController: NavigationControllerBackButtonDelegate {

    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {

        let msg = "message"

        /// show UIAlert
        alertAttention(msg: msg, actions: [

            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])

    }

}