【问题标题】:Setting action for back button in navigation controller在导航控制器中设置后退按钮的操作
【发布时间】:2010-11-15 22:38:49
【问题描述】:

我正在尝试覆盖导航控制器中后退按钮的默认操作。我在自定义按钮上提供了一个目标操作。奇怪的是,当通过 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 不这样做)?

【问题讨论】:

标签: ios iphone cocoa-touch uinavigationcontroller uibarbuttonitem


【解决方案1】:

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

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

【讨论】:

  • 这是一个巧妙、干净、漂亮且经过深思熟虑的解决方法
  • +1 很棒的 hack,但不提供对 pop 动画的控制
  • 如果我通过按钮向代理发送消息并且代理弹出控制器,这对我不起作用 - 这仍然会触发。
  • 另一个问题是你无法区分是用户按下了返回按钮还是你以编程方式调用了 [self.navigationController popViewControllerAnimated:YES]
  • 仅供参考:Swift 版本:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
【解决方案2】:

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

【讨论】:

  • 这是我见过的最干净的解决方案,比使用您自己的自定义 UIButton 更好、更简单。谢谢!
  • navigationBar:shouldPopItem: 不是私有方法,因为它是 UINavigationBarDelegate 协议的一部分。
  • 但是 UINavigationController 是否已经实现了委托(返回 YES)?还是将来会?子类化可能是更安全的选择
  • 我刚刚在 iOS 7.1 中实现了这个(非常酷的顺便说一句),并注意到在返回 NO 后,后退按钮保持在禁用状态(在视觉上,因为它仍然接收并响应触摸事件)。我通过将else 语句添加到shouldPop 检查并循环浏览导航栏子视图来解决它,如果需要在动画块内将alpha 值设置回1:gist.github.com/idevsoftware/9754057
  • 这是我见过的最好的扩展之一。非常感谢。
【解决方案3】:

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

【讨论】:

  • Apple 的文档 (developer.apple.com/iphone/library/documentation/UIKit/…) 说“此类不适用于子类化”。虽然我不确定它们的意思 - 它们可能意味着“你通常不需要这样做”,或者它们可能意味着“如果你弄乱我们的控制器,我们将拒绝你的应用”......
  • 这当然是唯一的方法。希望我能给你更多的积分汉斯!
  • 你真的可以用这个方法阻止视图退出吗?如果你想让视图不退出,你会让 popViewControllerAnimated 方法返回什么?
  • 是的,你可以。只是不要在您的实现中调用超类方法,请注意!您不应该这样做,用户希望返回导航。您可以做的是要求确认。根据 Apple 的文档 popViewController 返回:“从堆栈中弹出的视图控制器。”因此,当没有弹出任何内容时,您应该返回 nil;
  • @HansPickaers 我认为您关于阻止视图退出的回答可能有些不正确。如果我从 popViewControllerAnimated: 的子类实现中显示“确认”消息,则无论我返回什么,NavigationBar 仍会在树中向上一层设置动画。这似乎是因为单击后退按钮会调用导航栏上的 shouldPopNavigationItem。我按照建议从我的子类方法中返回 nil。
【解决方案4】:

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

【讨论】:

  • 也许我错过了什么,但它对我不起作用,从不调用扩展的 performSomeActionOnThePressOfABackButton 方法
  • @FlorentBreton 也许是个误会?只要没有错误,就应该调用shouldPopOnBackButtonPressperformSomeActionOnThePressOfABackButton 只是一种虚构的方法,并不存在。
  • 我明白了,所以我在控制器中创建了一个方法performSomeActionOnThePressOfABackButton,用于在按下后退按钮时执行特定操作,但从未调用此方法,该操作是正常的返回返回
  • 也不适合我。永远不会调用 shouldPop 方法。您是否在某处设置了委托?
  • @TimAutin 我刚刚再次测试了这个,似乎有些事情发生了变化。了解UINavigationController 中的关键部分,navigationBar.delegate 设置为导航控制器。所以方法应该被调用。但是,在 Swift 中,即使在子类中,我也无法调用它们。然而,我确实让它们在 Objective-C 中被调用,所以我现在只使用 Objective-C 版本。可能是一个 Swift 错误。
【解决方案5】:

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

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

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

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

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 您能否提供有关 if viewControllers.count
      • // 防止弹出太多导航项的同步问题 // 并且没有足够的视图控制器,反之亦然,以免出现异常点击
      【解决方案8】:

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

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

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

      【讨论】:

        【解决方案9】:

        最简单的方法

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

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

        【讨论】:

        • 确保您的视图控制器将自己设置为继承的navigationController的委托并符合UINavigationControllerDelegate协议
        【解决方案10】:

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

        【讨论】:

        • UIViewController 中的覆盖方法 navigationShouldPopOnBackButton 不起作用 - 程序执行父方法而不是覆盖的方法。有什么解决办法吗?有人有同样的问题吗?
        • 如果返回true则一直返回rootview
        • @Pawriwes 这是我写的一个似乎对我有用的解决方案:stackoverflow.com/a/34343418/826435
        【解决方案11】:

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

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

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

        【讨论】:

          【解决方案12】:

          我不相信这是可能的,很容易。我相信解决这个问题的唯一方法是制作你自己的后退按钮箭头图像放置在那里。一开始我很沮丧,但我明白为什么为了一致性起见,它被遗漏了。

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

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

          【讨论】:

          • 是的,问题是我希望它看起来像普通的后退按钮,只需要它首先调用我的自定义操作...
          【解决方案13】:

          有一种更简单的方法,只需将UINavigationBar委托方法 子类化并覆盖 ShouldPopItem方法

          【讨论】:

          • 我想你的意思是说子类化 UINavigationController 类并实现一个 shouldPopItem 方法。这对我来说效果很好。但是,该方法不应像您期望的那样简单地返回 YES 或 NO。此处提供解释和解决方案:stackoverflow.com/a/7453933/462162
          【解决方案14】:

          这种方法对我有用(但“返回”按钮没有“

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

          【讨论】:

            【解决方案15】:

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

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

            【讨论】:

              【解决方案16】:

              使用 Swift:

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

              【讨论】:

              • 从 iOS 10 开始,也许更早,这不再有效。
              【解决方案17】:

              这是 @oneway 的 Swift 3 版本,用于在导航栏后退按钮事件被触发之前捕获它。由于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")       
              }
              

              【讨论】:

              • 我收到一个错误:Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' 当我尝试编译你的代码时,if vc.responds(to: #selector(v... 行另外,self.topViewController 返回一个可选的并且也有一个警告。
              • FWIW,我已经通过制作:let vc = self.topViewController as! MyViewController 修复了该代码,到目前为止它似乎工作正常。如果您认为这是正确的更改,则可以编辑代码。另外,如果您觉得不应该这样做,我会很高兴知道原因。感谢您提供此代码。你可能应该写一篇关于这个的博客文章,因为这个答案会根据投票被隐藏起来。
              • @SankarP 你得到这个错误的原因是你的MyViewController 可能不符合BackButtonDelegate。而不是强制展开,您应该执行guard let vc = self.topViewController as? MyViewController else { return true } 以避免可能的崩溃。
              • 谢谢。我认为保护声明应该变成:guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true },以确保屏幕移动到正确的页面,以防它无法正确投射。我现在明白 navigationBar 函数在所有 VC 中都被调用,而不仅仅是存在此代码的视图控制器。更新答案中的代码可能会很好吗?谢谢。
              【解决方案18】:

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

              【讨论】:

                【解决方案19】:

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

                我主要同意 Amagrammer,但我认为自定义带有箭头的按钮并不难。我只需重命名后退按钮,拍摄屏幕截图,对所需的按钮大小进行 Photoshop 处理,然后将其作为按钮顶部的图像。

                【讨论】:

                • 我同意你可以使用 photoshop,我想如果我真的想要的话,我可能会这样做,但现在我决定稍微改变一下外观和感觉,让它按照我想要的方式工作。跨度>
                • 是的,只是在附加到 backBarButtonItem 时不会触发这些操作。我不知道这是错误还是功能;可能连苹果都不知道。至于photoshopping练习,我会担心Apple会因为滥用规范符号而拒绝该应用程序。
                • 注意:此答案已从副本中合并。
                【解决方案20】:

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

                【讨论】:

                • 注意:此答案已从副本中合并。
                【解决方案21】:

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

                【讨论】:

                • 注意:此答案已从副本中合并。
                【解决方案22】:

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

                【讨论】:

                • 注意:此答案已从副本中合并。
                【解决方案23】:

                至少在 Xcode 5 中,有一个简单且相当不错(不完美)的解决方案。在 IB 中,将 Bar Button Item 从 Utilities 窗格中拖放到导航栏左侧的 Back 按钮所在的位置。将标签设置为“返回”。您将拥有一个功能按钮,您可以将其绑定到您的 IBAction 并关闭您的 viewController。我正在做一些工作,然后触发一个 unwind segue,它运行良好。

                不理想的是这个按钮没有得到

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

                【讨论】:

                  【解决方案24】:

                  斯威夫特

                  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
                  }
                  

                  【讨论】:

                    【解决方案25】:

                    @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,默认采用这种行为。

                    【讨论】:

                      【解决方案26】:

                      使用isMovingFromParentViewController

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

                      【讨论】:

                      • 您能否进一步解释一下这是如何证明后退按钮被点击的?
                      • 这很简单,但只有当您确定从您可能加载的任何子视图返回到该视图时,它才有效。如果子视图在返回父视图时跳过此视图,则不会调用您的代码(视图在没有从父视图移动的情况下已经消失)。但这与 OP 要求的仅在触发后退按钮时处理事件相同的问题。所以这是对他问题的简单回答。
                      • 这个超级简单优雅。我喜欢它。只有一个问题:如果用户滑动返回,即使他们在中途取消,这也会触发。也许更好的解决方案是将此代码放入viewDidDisappear。这样它只会在视图确定消失时触发。
                      【解决方案27】:

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

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

                        if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
                        {
                            self.navigationController.interactivePopGestureRecognizer.enabled = NO;
                        }
                        
                      2. 或者干脆改用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.
                            }
                        }
                        

                      【讨论】:

                        【解决方案28】:

                        到目前为止我找到的解决方案不是很好,但它对我有用。拿着这个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];
                        

                        【讨论】:

                          【解决方案29】:

                          找到了新的方法:

                          目标-C

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

                          斯威夫特

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

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 2023-03-23
                            • 2012-01-01
                            • 2011-11-29
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多