首页 文章

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

提问于
浏览
174

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

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

一旦我通过 navigationItem 上的 leftBarButtonItem 设置它就会调用我的动作,但是按钮看起来像一个普通的圆形而不是向后箭头:

self.navigationItem.leftBarButtonItem = backButton;

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

28 回答

  • 1

    使用 isMovingFromParentViewController

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(true)
    
        if self.isMovingFromParentViewController {
            // current viewController is removed from parent
            // do some work
        }
    }
    
  • 13

    尝试将其放入要检测印刷机的视图控制器中:

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

    我已经实现了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 controler
    }
    

    Download sample app .

  • 0

    与Amagrammer说的不同,这是可能的 . 你必须继承你的 navigationController . 我解释了所有here(包括示例代码) .

  • 2

    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
        }
    }
    
  • 1

    不可能直接做 . 有几种选择:

    • 创建您自己的自定义 UIBarButtonItem ,如果测试通过,则会自动点击并弹出

    • 使用 UITextField 委托方法验证表单字段内容,例如-textFieldShouldReturn:,在键盘上按下 ReturnDone 按钮后调用该方法

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

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

  • 171

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

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

  • 1

    此技术允许您更改“后退”按钮的文本,而不会影响任何视图控制器的 Headers 或在动画期间看到后退按钮文本更改 .

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

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

    Easiest way

    您可以使用UINavigationController的委托方法 . 按下VC的后退按钮时会调用方法 willShowViewController . 按下btn后按下你想要的任何内容

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

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

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

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

  • 2

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

    您可以通过创建常规按钮并隐藏默认后退按钮来关闭(不使用箭头):

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

    通过继承 UINavigationBar 的委托方法并覆盖 ShouldPopItem 方法,有一种更简单的方法 .

  • 3

    这是我的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
        }
    
    }
    
  • 1

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

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

  • 5

    使用Swift:

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

    这是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")       
    }
    
  • 363

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

    我主要同意Amagrammer,但我不认为用按钮定制按钮会很难 . 我只需要重命名后退按钮,拍摄一个屏幕截图,然后按照所需的按钮尺寸,将其作为按钮顶部的图像 .

  • 2

    您可以尝试访问NavigationBars右键按钮项并设置其选择器属性...这是一个参考UIBarButtonItem reference,另一件事,如果这个最干净的工作将def工作,将导航栏的右按钮项设置为您创建的自定义UIBarButtonItem并设置其选择器...希望这有帮助

  • 1

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

  • 0

    至少在Xcode 5中,有一个简单而且非常好(不完美)的解决方案 . 在IB中,将“按钮项”拖出“实用工具”窗格,然后将其放在导航栏左侧的“后退”按钮所在的位置 . 将标签设置为“返回” . 你将有一个功能按钮,你可以绑定到你的IBAction并关闭你的viewController . 我正在做一些工作,然后触发一个放松的segue,它完美地工作 .

    不理想的是,这个按钮没有得到<箭头,也没有结转以前的VC Headers ,但我认为这可以管理 . 为了我的目的,我将新的后退按钮设置为“完成”按钮,因此它的目的很明确 .

    您最终还会在IB导航器中使用两个Back按钮,但为了清晰起见,它很容易标记 .

    enter image description here

  • 3

    迅速

    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
    }
    
  • 3

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

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

    Swift 4 iOS 11.3版本:

    这是基于来自https://stackoverflow.com/a/34343418/4316579的kgaidis的答案

    我不确定扩展何时停止工作,但在撰写本文时(Swift 4),似乎扩展将不再执行,除非您声明UINavigationBarDelegate符合性如下所述 .

    希望这有助于那些想知道为什么他们的扩展不再有效的人 .

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

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

    • 禁用 viewDidAppear 中的滑动到后退手势,并且只允许使用后退按钮,方法是:
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    
    • 或者只需使用 viewDidDisappear ,如下所示:
    - (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.
        }
    }
    
  • 2

    要拦截后退按钮,只需用透明的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
    
  • 5

    到目前为止我找到的解决方案不是很好,但它对我有用 . 拿这个answer,我也检查一下我是否以编程方式弹出:

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

    找到了新方法:

    Objective-C

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

    斯威夫特

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

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

    现在在任何控制器中,只需符合 RequestsNavigationPopVerification ,默认情况下会采用此行为 .

相关问题