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

发布于 2024-09-02 18:14:00 字数 760 浏览 3 评论 0原文

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

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

一旦我通过 leftBarButtonItem 设置它它调用我的操作的 navigationItem ,但是该按钮看起来像一个普通的圆形按钮,而不是带箭头的返回按钮:

self.navigationItem.leftBarButtonItem = backButton;

如何让它在返回根视图之前调用我的自定义操作?有没有办法覆盖默认的后退操作,或者是否有一个方法在离开视图时总是被调用(viewDidUnload不这样做)?

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:

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 leftBarButtonItem on the navigationItem it calls my action, however then the button looks like a plain round one instead of the arrowed back one:

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 (viewDidUnload doesn't do that)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

过期以后 2024-09-09 18:14:00

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

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

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];
}
时光磨忆 2024-09-09 18:14:00

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

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

下载示例应用

I've implemented UIViewController-BackButtonHandler extension. It does not need to subclass anything, just put it into your project and override navigationShouldPopOnBackButton method in UIViewController class:

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

Download sample app.

Smile简单爱 2024-09-09 18:14:00

与阿玛格拉默所说的不同,这是可能的。您必须对您的 navigationController 进行子类化。我在此处解释了所有内容(包括示例代码)。

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

拍不死你 2024-09-09 18:14:00

Swift 版本:(

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

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

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

然后创建一个类,例如 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
    }
}

Swift Version:

(of 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:

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
    }
}
吐个泡泡 2024-09-09 18:14:00

直接做是不可能的。有几种替代方案:

  1. 创建您自己的自定义 UIBarButtonItem,如果测试通过,则在点击和弹出时进行验证
  2. 使用 UITextField 委托方法验证表单字段内容,例如 -textFieldShouldReturn:,在按下键盘上的ReturnDone按钮后调用

第一个选项的缺点问题是无法从自定义栏按钮访问后退按钮的左指箭头样式。所以你必须使用图像或使用常规样式的按钮。

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

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

  1. Create your own custom UIBarButtonItem that validates on tap and pops if the test passes
  2. Validate the form field contents using a UITextField delegate method, such as -textFieldShouldReturn:, which is called after the Return or Done button is pressed on the keyboard

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.

凯凯我们等你回来 2024-09-09 18:14:00

由于某些线程原因,@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
    }
}

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

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.

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 behind the back button! Anyway, it still catches any touch targetting the original button.

挽清梦 2024-09-09 18:14:00

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

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

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

异步方式在 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
    }
}


在您的控制器上


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

}

Overriding navigationBar(_ navigationBar:shouldPop): This is not a 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:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

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

Building on previous responses with UIAlert in Swift5 in a Asynchronous way


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

}
就是爱搞怪 2024-09-09 18:14:00

此技术允许您更改“后退”按钮的文本,而不会影响任何视图控制器的标题,也不会在动画期间看到后退按钮文本发生变化。

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

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

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 calling view controller:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
寻找一个思念的角度 2024-09-09 18:14:00

最简单的方法

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

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

Easiest way

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

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
听,心雨的声音 2024-09-09 18:14:00

这是我的 Swift 解决方案。在 UIViewController 的子类中,重写 navigationShouldPopOnBackButton 方法。

extension UIViewController {
    func navigationShouldPopOnBackButton() -> Bool {
        return true
    }
}

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}

Here's my Swift solution. In your subclass of UIViewController, override the navigationShouldPopOnBackButton method.

extension UIViewController {
    func navigationShouldPopOnBackButton() -> Bool {
        return true
    }
}

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}
深海夜未眠 2024-09-09 18:14:00

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

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


}

现在通过以下方法提供所需的功能:

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

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

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

雨轻弹 2024-09-09 18:14:00

我不相信这是可能的、容易的。我认为解决这个问题的唯一方法是制作您自己的后退按钮箭头图像并将其放置在那里。起初这让我很沮丧,但我明白为什么为了保持一致性,它被排除在外。

您可以通过创建常规按钮并隐藏默认后退按钮来接近(没有箭头):

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;

I don't believe this is possible, easily. The only way I believe to get around this is to make your own back button arrow image to place up there. It was frustrating for me at first but I see why, for consistency's sake, it was left out.

You can get close (without the arrow) by creating a regular button and hiding the default back button:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
穿透光 2024-09-09 18:14:00

有一种更简单的方法,只需子类化 UINavigationBar委托方法覆盖ShouldPopItem方法< /em>.

There's an easier way by just subclassing the delegate method of the UINavigationBar and override the ShouldPopItemmethod.

可爱暴击 2024-09-09 18:14:00

这种方法对我有用(但“后退”按钮不会有“<”符号):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}

This approach worked for me (but the "Back" button will not have the "<" sign):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}
世界如花海般美丽 2024-09-09 18:14:00

onegray的解决方案并不安全。根据苹果官方文档,https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html,
我们应该避免这样做。

“如果类别中声明的方法的名称与原始类中的方法相同,或者与同一类(甚至超类)上的另一个类别中的方法相同,则使用哪个方法实现的行为是未定义的如果您在自己的类中使用类别,那么这不太可能成为问题,但在使用类别向标准 Cocoa 或 Cocoa Touch 类添加方法时可能会导致问题。”

onegray's solution is not safe.According to the official documents by Apple,https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html,
we should avoid doing that.

"If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes."

请止步禁区 2024-09-09 18:14:00

使用斯威夫特:

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

Using Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
云淡风轻 2024-09-09 18:14:00

这是 Swift 3 版本的 @oneway 的答案,用于在导航栏后退按钮事件被触发之前捕获它。由于 UINavigationBarDelegate 不能用于 UIViewController,因此您需要创建一个委托,当 navigationBar shouldPop 为叫。

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

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

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

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

然后,在您的视图控制器中添加委托功能:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

我意识到我们经常想要添加一个警报控制器,以便用户决定是否要返回。如果是这样,您始终可以在 navigationShouldPopOnBackButton() 函数中 return false 并通过执行以下操作来关闭视图控制器:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}

Here is Swift 3 version of @oneway's answer for catching navigation bar back button event before it gets fired. As UINavigationBarDelegate cannot be used for UIViewController, you need to create a delegate that will be triggered when navigationBar shouldPop is called.

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

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

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

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

And then, in your view controller add the delegate function:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

I've realised that we often want to add an alert controller for users to decide whether they wanna go back. If so, you can always return false in navigationShouldPopOnBackButton() function and close your view controller by doing something like this:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}
执笏见 2024-09-09 18:14:00

Swift 4 iOS 11.3 版本:

这是基于 https://stackoverflow.com/a/34343418/4316579

我不确定扩展何时停止工作,但在撰写本文时(Swift 4),除非您按照如下所述声明 UINavigationBarDelegate 一致性,否则该扩展似乎将不再执行。

希望这可以帮助那些想知道为什么他们的扩展不再有效的人。

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

    }
}

Swift 4 iOS 11.3 Version:

This builds on the answer from kgaidis from https://stackoverflow.com/a/34343418/4316579

I am not sure when the extension stopped working, but at the time of this writing (Swift 4), it appears that the extension will no longer be executed unless you declare UINavigationBarDelegate conformity as described below.

Hope this helps people that are wondering why their extension no longer works.

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

    }
}
泪痕残 2024-09-09 18:14:00

通过使用当前保留为“nil”的目标和操作变量,您应该能够连接保存对话框,以便在“选择”按钮时调用它们。请注意,这可能会在奇怪的时刻触发。

我基本上同意 Amagrammer 的观点,但我认为定制带有箭头的按钮并不难。我只需重命名后退按钮,拍摄屏幕截图,用 Photoshop 处理所需的按钮大小,然后将其作为按钮顶部的图像。

By using the target and action variables that you are currently leaving 'nil', you should be able to wire your save-dialogs in so that they are called when the button is "selected". Watch out, this may get triggered at strange moments.

I agree mostly with Amagrammer, but I don't think it would be that hard to make the button with the arrow custom. I would just rename the back button, take a screen shot, photoshop the button size needed, and have that be the image on the top of your button.

坚持沉默 2024-09-09 18:14:00

您可以尝试访问 NavigationBars Right Button 项目并设置其选择器属性...这里有一个参考 UIBarButtonItem 参考,如果这确实有效的话,另一件事就是设置导航栏的右侧按钮项到您创建并设置其选择器的自定义 UIBarButtonItem...希望这有帮助

You can try accessing the NavigationBars Right Button item and set its selector property...heres a reference UIBarButtonItem reference, another thing if this doenst work that will def work is, set the right button item of the nav bar to a custom UIBarButtonItem that you create and set its selector...hope this helps

煮茶煮酒煮时光 2024-09-09 18:14:00

对于需要这样的用户输入的表单,我建议将其作为“模态”而不是导航堆栈的一部分来调用。这样他们就必须处理表单上的业务,然后您可以验证它并使用自定义按钮将其关闭。您甚至可以设计一个导航栏,它看起来与应用程序的其余部分相同,但为您提供更多控制。

For a form that requires user input like this, I would recommend invoking it as a "modal" instead of part of your navigation stack. That way they have to take care of business on the form, then you can validate it and dismiss it using a custom button. You can even design a nav bar that looks the same as the rest of your app but gives you more control.

邮友 2024-09-09 18:14:00

要拦截“后退”按钮,只需用透明的 UIControl 覆盖它并拦截触摸即可。

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end

To intercept the Back button, simply cover it with a transparent UIControl and intercept the touches.

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end
帅冕 2024-09-09 18:14:00

至少在 Xcode 5 中,有一个简单且相当不错(不完美)的解决方案。在 IB 中,将“栏按钮项”从“实用程序”窗格中拖出,并将其放在导航栏左侧“后退”按钮所在的位置。将标签设置为“返回”。您将有一个功能按钮,可以将其绑定到 IBAction 并关闭 viewController。我正在做一些工作,然后触发展开转场,它工作得很好。

不理想的是这个按钮没有得到 < arrow并没有继承之前的VC头衔,但我认为这是可以管理的。出于我的目的,我将新的“后退”按钮设置为“完成”按钮,因此其目的很明确。

您最终还会在 IB 导航器中看到两个后退按钮,但为了清晰起见,很容易对其进行标记。

在此处输入图像描述

At least in Xcode 5, there is a simple and pretty good (not perfect) solution. In IB, drag a Bar Button Item off the Utilities pane and drop it on the left side of the Navigation Bar where the Back button would be. Set the label to "Back." You will have a functioning button that you can tie to your IBAction and close your viewController. I'm doing some work and then triggering an unwind segue and it works perfectly.

What isn't ideal is that this button does not get the < arrow and does not carry forward the previous VCs title, but I think this can be managed. For my purposes, I set the new Back button to be a "Done" button so it's purpose is clear.

You also end up with two Back buttons in the IB navigator, but it is easy enough to label it for clarity.

enter image description here

煮酒 2024-09-09 18:14:00

迅速

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}

Swift

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}
ぃ双果 2024-09-09 18:14:00

找到了新的方法:

Objective-C

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

Swift

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}

Found new way to do it :

Objective-C

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

Swift

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
寂寞清仓 2024-09-09 18:14:00

@onegray 答案的 Swift 版本

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

现在,在任何控制器中,只需遵守 RequestsNavigationPopVerification 即可默认采用此行为。

Swift version of @onegray's answer

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

Now in any controller, just conform to RequestsNavigationPopVerification and this behaviour is adopted by default.

拥醉 2024-09-09 18:14:00

使用 isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}

Use isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
玻璃人 2024-09-09 18:14:00

@William 的答案是正确的,但是,如果用户开始滑动返回手势,则会调用 viewWillDisappear 方法,甚至 self 也不会出现在导航堆栈(即,self.navigationController.viewControllers 不会包含 self),即使滑动未完成且视图控制器未实际弹出。因此,解决方案是:

  1. viewDidAppear 中禁用滑动返回手势,仅允许使用后退按钮,方法是:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    
  2. 或者简单地使用 viewDidDisappear代码>改为,如下:

    - (void)viewDidDisappear:(BOOL)动画
    {
        [超级viewDidDisappear:动画];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // 按下后退按钮或滑动返回手势
            // 完全的。我们知道这是真的,因为自我不再是
            // 在导航堆栈中。
        }
    }
    

The answer from @William is correct however, if the user starts a swipe-to-go-back gesture the viewWillDisappear method is called and even self won't be in the navigation stack (that is, self.navigationController.viewControllers won't contain self), even if the swipe is not completed and the view controller is not actually popped. Thus, the solution would be to:

  1. Disable the swipe-to-go-back gesture in viewDidAppear and only allow using the back button, by using:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    
  2. Or simply use viewDidDisappear instead, as follows:

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }
    
潇烟暮雨 2024-09-09 18:14:00

使用 iOS 16 或更高版本时,您可以使用当前 UIViewControllernavigationItem 属性的 backAction 属性来设置自定义后退操作。这是一个例子:

navigationItem.backAction = UIAction(handler: { [weak self] action in
    
    // Your custom action here
    self?.myCustomAction()

    // Optionally navigate one view controller back
    self?.navigationController?.popViewController(animated: true)
})

When using iOS 16 or newer, you can use the backAction property of the navigationItem property of the current UIViewController to set a custom back action. Here is an example:

navigationItem.backAction = UIAction(handler: { [weak self] action in
    
    // Your custom action here
    self?.myCustomAction()

    // Optionally navigate one view controller back
    self?.navigationController?.popViewController(animated: true)
})
空宴 2024-09-09 18:14:00

到目前为止我找到的解决方案不是很好,但它对我有用。考虑到这个答案,我还检查我是否以编程方式弹出:

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

The solution I have found so far is not very nice, but it works for me. Taking this answer, I also check whether I'm popping programmatically or not:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

You have to add that property to your controller and set it to YES before popping programmatically:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文