【问题标题】:Execute action when back bar button of UINavigationController is pressed按下 UINavigationController 的后退栏按钮时执行操作
【发布时间】:2015-02-27 02:35:18
【问题描述】:

当按下UINavigationController 的后退按钮时,我需要执行一个操作(清空数组),而该按钮仍会导致堆栈上的前一个ViewController 出现。我怎么能用 swift 完成这个?

【问题讨论】:

    标签: ios xcode swift uinavigationcontroller


    【解决方案1】:

    您可以简单地从堆栈中删除不必要的控制器,如下所示:

    self.navigationController?.viewControllers.removeAll(where: {
            $0 is FirstViewController || $0 is SecondWithPinController
        })
    

    【讨论】:

      【解决方案2】:

      按下后退按钮时,忽略带有屏幕边缘手势的交互式弹出。

      override func viewWillDisappear(_ animated: Bool) {
          super.viewWillDisappear(animated)
          
          if isMovingFromParent, transitionCoordinator?.isInteractive == false {
            // code here
          }
        }
      

      【讨论】:

        【解决方案3】:

        在离开当前控制器之前,我需要显示警报。所以我是这样做的:

        1. 使用UINavigationBarDelegate 将扩展名添加到UINavigationController
        2. 向控制器添加选择器navigationShouldPopOnBack(completion:)

        成功了)

        extension UINavigationController: UINavigationBarDelegate {
            public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
                if let items = navigationBar.items, viewControllers.count < items.count {
                    return true
                }
        
                let clientInfoVC = topViewController as? ClientInfoVC
                if clientInfoVC?.responds(to: #selector(clientInfoVC?.navigationShouldPopOnBack)) ?? false {
                    clientInfoVC?.navigationShouldPopOnBack(completion: { isAllowPop in
                        if isAllowPop {
                            DispatchQueue.main.async {
                                self.popViewController(animated: true)
                            }
                        }
                    })
                }
        
                DispatchQueue.main.async {
                    self.popViewController(animated: true)
                }
        
                return false
            }
        }
        
        @objc func navigationShouldPopOnBack(completion: @escaping (Bool) -> ()) {
                let ok = UIAlertAction(title: R.string.alert.actionOk(), style: .default) { _ in
                    completion(true)
                }
                let cancel = UIAlertAction(title: R.string.alert.actionCancel(), style: .cancel) { _ in
                    completion(false)
                }
                let alertController = UIAlertController(title: "", message: R.string.alert.contractMessage(), preferredStyle: .alert)
                alertController.addAction(ok)
                alertController.addAction(cancel)
                present(alertController, animated: true, completion: nil)
            }
        

        【讨论】:

        • 嗨@mgyky/@Taras,知道为什么navigationShouldPopOnBack 不再从我这里调用吗? iOS12 可能是问题吗?
        • 嗨@David。在我这边,iOS 12 一切正常。你能发送你的代码吗?
        • 嗨@Taras,您为什么还没有迁移到 iOS 13 或 14?也许你的设备不能?如果可以,请尝试使用最新的 iOS 来查看行为。我的原始代码是这个gist.github.com/HamGuy/a099058e674b573ffe433132f7b5651e,我也试过这个programmersought.com/article/1529883806
        【解决方案4】:

        Swift 5 __ Xcode 11.5

        就我而言,我想制作动画,完成后,返回。 一种覆盖后退按钮默认操作的方法 并调用您的自定义操作是这样的:

             override func viewDidAppear(_ animated: Bool) {
                super.viewDidAppear(animated)
                setBtnBack()
            }
        
            private func setBtnBack() {
                for vw in navigationController?.navigationBar.subviews ?? [] where "\(vw.classForCoder)" == "_UINavigationBarContentView" {
                    print("\(vw.classForCoder)")
                    for subVw in vw.subviews where "\(subVw.classForCoder)" == "_UIButtonBarButton" {
                        let ctrl = subVw as! UIControl
                        ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
                        ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
                    }
                }
            }
        
        
            @objc func backBarBtnAction() {
                doSomethingBeforeBack { [weak self](isEndedOk) in
                    if isEndedOk {
                        self?.navigationController?.popViewController(animated: true)
                    }
                }
            }
        
        
            private func doSomethingBeforeBack(completion: @escaping (_ isEndedOk:Bool)->Void ) {
                UIView.animate(withDuration: 0.25, animations: { [weak self] in
                    self?.vwTxt.alpha = 0
                }) { (isEnded) in
                    completion(isEnded)
                }
            }
        

        或者您可以使用此方法一次探索 NavigationBar 视图层次结构,并获取索引以访问 _UIButtonBarButton 视图,强制转换为 UIControl,删除目标操作,并添加您的自定义目标操作:

            private func debug_printSubviews(arrSubviews:[UIView]?, level:Int) {
                for (i,subVw) in (arrSubviews ?? []).enumerated() {
                    var str = ""
                    for _ in 0...level {
                        str += "\t"
                    }
                    str += String(format: "%2d %@",i, "\(subVw.classForCoder)")
                    print(str)
                    debug_printSubviews(arrSubviews: subVw.subviews, level: level + 1)
                }
            }
        
            // Set directly the indexs
            private func setBtnBack_method2() {
                // Remove or comment the print lines
                debug_printSubviews(arrSubviews: navigationController?.navigationBar.subviews, level: 0)   
                let ctrl = navigationController?.navigationBar.subviews[1].subviews[0] as! UIControl
                print("ctrl.allTargets: \(ctrl.allTargets)")
                ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
                print("ctrl.allTargets: \(ctrl.allTargets)")
                ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
                print("ctrl.allTargets: \(ctrl.allTargets)")
            }
        

        【讨论】:

        • 这按预期工作,但在 iOS 15 中,当我点击并按住后退按钮时,它会弹出菜单操作并执行后退操作。
        【解决方案5】:

        这是我的解决方案

        extension UINavigationController: UINavigationBarDelegate {
            public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
                if let shouldBlock = self.topViewController?.shouldPopFromNavigation() {
                    return shouldBlock
                }
                return true
            }
        }
        
        extension UIViewController {
            @objc func shouldPopFromNavigation() -> Bool {
                return true
            }
        }
        

        在你的视图控制器中,你可以这样处理:

        @objc override func shouldPopFromNavigation() -> Bool {
                // Your dialog, example UIAlertViewController or whatever you want
                return false
            }
        

        【讨论】:

        • 嗨@Hiro,任何线索为什么不再从我身边调用shouldPopFromNavigation? iOS12 可能是问题吗?
        • @David:它应该适用于所有人。现在状态如何?您是否将 shouldPopFromNavigation 放在 UIViewController 的扩展中?
        • 我在运行 iOS 12 的 iphone 5 上尝试了相同的代码,并且成功了。运行 iOS 14 的 XR 上的相同代码没有。
        • 我使用 navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) 而不是 shouldPop 并且在前一个屏幕上强制大标题效果很好,谢谢!
        【解决方案6】:

        您可以继承 UINavigationController 并覆盖 popViewController(animated: Bool)。除了能够在那里执行一些代码之外,您还可以阻止用户完全返回,例如提示保存或放弃他当前的工作。

        示例实现,您可以在其中设置由推送控制器设置/清除的popHandler

        class NavigationController: UINavigationController
        {
            var popHandler: (() -> Bool)?
        
            override func popViewController(animated: Bool) -> UIViewController?
            {
                guard self.popHandler?() != false else
                {
                    return nil
                }
                self.popHandler = nil
                return super.popViewController(animated: animated)
            }
        }
        

        以及来自跟踪未保存工作的推送控制器的示例使用情况。

        let hasUnsavedWork: Bool = // ...
        (self.navigationController as! NavigationController).popHandler = hasUnsavedWork ?
            {
                // Prompt saving work here with an alert
        
                return false // Prevent pop until as user choses to save or discard
        
            } : nil // No unsaved work, we clear popHandler to let it pop normally
        

        作为一个很好的触摸,当用户尝试使用滑动手势返回时,interactivePopGestureRecognizer 也会调用它。

        【讨论】:

        • 优秀的答案,谢谢里维拉
        【解决方案7】:

        对于Swift 5,我们可以在视图中勾选会消失

        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
        
            if self.isMovingFromParent {
                delegate?.passValue(clickedImage: selectedImage)
            }
        }
        

        【讨论】:

          【解决方案8】:
          override func willMove(toParent parent: UIViewController?)
          {
              super.willMove(toParent: parent)
              if parent == nil
              {
                  print("This VC is 'will' be popped. i.e. the back button was pressed.")
              }
          }
          

          【讨论】:

          • 在 Swift3/iOS10 中不工作,控制台打印“嵌套弹出动画会导致导航栏损坏”。
          • 根本没有被调用
          • 这在进入新的 VC 时也会被调用,而不仅仅是在返回时调用。
          • 根据@JozemiteApps 的评论,它在文档中在从容器视图控制器中添加或删除视图控制器之前调用。.
          • 这应该是公认的答案。当parent == nil 是我们移动返回parent 场景时
          【解决方案9】:

          在 Swift 5 和 Xcode 10.2 中

          请不要添加自定义栏按钮项,使用此默认行为。

          不需要viewWillDisappear,不需要自定义BarButtonItem等...

          最好检测 VC 何时从其父级中删除。

          使用这两个函数中的任何一个

          override func willMove(toParent parent: UIViewController?) {
              super.willMove(toParent: parent)
              if parent == nil {
                  callStatusDelegate?.backButtonClicked()//Here write your code
              }
          }
          
          override func didMove(toParent parent: UIViewController?) {
              super.didMove(toParent: parent)
              if parent == nil {
                  callStatusDelegate?.backButtonClicked()//Here write your code
              }
          }
          

          如果您想停止后退按钮的默认行为,请添加自定义 BarButtonItem。

          【讨论】:

          • 请注意,当您以编程方式弹出时也会调用它,而不仅仅是按下返回按钮。
          【解决方案10】:
          override func viewWillDisappear(_ animated: Bool) {
              super.viewWillDisappear(animated)
          
              if self.isMovingToParent {
          
                  //your code backView
              }
          }
          

          【讨论】:

            【解决方案11】:

            你可以在你的 Viewcontroller 中做一些事情,比如

            override func navigationShouldPopOnBackButton() -> Bool {
                self.backAction() //Your action you want to perform.
                return true
            }
            

            完整答案使用 Detecting when the 'back' button is pressed on a navbar

            【讨论】:

              【解决方案12】:

              这是最简单的 Swift 5 解决方案,它不需要您创建自定义后退按钮并放弃您免费获得的所有 UINavigationController 左按钮功能。

              正如上面 Brandon A 建议的那样,您需要在要与之交互的视图控制器中实现UINavigationControllerDelegate,然后再返回它。一个好方法是创建一个展开转场,您可以手动或自动执行它,并从自定义完成按钮或后退按钮重用相同的代码。

              首先,在其viewDidLoad 中使您感兴趣的视图控制器(您想要检测返回的视图控制器)成为导航控制器的委托:

              override func viewDidLoad() {
                  super.viewDidLoad()
                  navigationController?.delegate = self
              }
              

              其次,在文件底部添加一个覆盖navigationController(willShow:animated:)的扩展名

              extension PickerTableViewController: UINavigationControllerDelegate {
              
                  func navigationController(_ navigationController: UINavigationController,
                                            willShow viewController: UIViewController,
                                            animated: Bool) {
              
                      if let _ = viewController as? EditComicBookViewController {
              
                          let selectedItemRow = itemList.firstIndex(of: selectedItemName)
                          selectedItemIndex = IndexPath(row: selectedItemRow!, section: 0)
              
                          if let selectedCell = tableView.cellForRow(at: selectedItemIndex) {
                              performSegue(withIdentifier: "PickedItem", sender: selectedCell)
                          }
                      }
                  }
              }
              

              由于您的问题包含UITableViewController,因此我提供了一种获取用户点击的行的索引路径的方法。

              【讨论】:

                【解决方案13】:

                这就是我为自己的问题解决它的方法

                override func viewWillAppear(_ animated: Bool) {
                    super.viewWillAppear(animated)
                    self.navigationItem.leftBarButtonItem?.action = #selector(self.back(sender:))
                    self.navigationItem.leftBarButtonItem?.target = self
                }
                
                @objc func back(sender: UIBarButtonItem) {
                
                }
                

                【讨论】:

                  【解决方案14】:

                  按照另一个答案的建议将按钮替换为自定义按钮可能不是一个好主意,因为您将失去默认行为和样式。

                  您还有一个选择是在视图控制器上实现 viewWillDisappear 方法并检查名为 isMovingFromParentViewController 的属性。如果该属性为真,则意味着视图控制器正在消失,因为它正在被移除(弹出)。

                  应该看起来像:

                  override func viewWillDisappear(_ animated: Bool) {
                      super.viewWillDisappear(animated)
                  
                      if self.isMovingFromParentViewController {
                          // Your code...
                      }
                  }
                  

                  在 Swift 4.2 中

                  override func viewWillDisappear(_ animated: Bool) {
                      super.viewWillDisappear(animated)
                  
                      if self.isMovingFromParent {
                          // Your code...
                      }
                  }
                  

                  【讨论】:

                  • @gmogames 是的,你不能那样做。不过,这个问题并没有要求。为了能够停止返回的动作,我想你真的需要覆盖按钮。
                  • 对于 Swift 3.1override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController { // Your code... } }
                  • viewWillDisappear(animated:) 会在你接到电话时被触发。这可能不是您想要的。可能最好使用willMove(toParentViewController:)
                  • 这应该是公认的答案。干净简单。
                  • 不,这是完全错误的。当按下后退按钮并弹出到 ViewController 时调用此方法,然后此方法称为问题是如何在 NavigationItem BackButton Click 中执行操作。这就像完成,我们可以在 ViewWillDisappear 中做这件事。我希望你明白我要告诉你的。有什么方法可以点击按钮然后请给我解决方案。谢谢
                  【解决方案15】:

                  斯威夫特 4.2:

                  override func viewWillDisappear(_ animated: Bool) {
                      super.viewWillDisappear(animated)
                  
                      if self.isMovingFromParent {
                          // Your code...
                  
                      }
                  }
                  

                  【讨论】:

                    【解决方案16】:

                    我能够通过以下方式实现这一目标:

                    斯威夫特 3

                    override func didMoveToParentViewController(parent: UIViewController?) {
                       super.didMoveToParentViewController(parent)
                    
                       if parent == nil {
                          println("Back Button pressed.")
                          delegate?.goingBack()
                       }           
                    }
                    

                    斯威夫特 4

                    override func didMove(toParent parent: UIViewController?) {
                        super.didMove(toParent: parent)
                    
                        if parent == nil {
                            debugPrint("Back Button pressed.")
                        }
                    }
                    

                    不需要自定义后退按钮。

                    【讨论】:

                    • 这太棒了。旧评论,但仍然适用于最新的 Swift。
                    • 当从下一个视图控制器(在这个视图控制器上)展开时也会触发(误报),因此不是真正的后退按钮按下检测。
                    • 同上一个人的备注,这段代码没有检测到后退按钮的激活,而是当前视图的弹出。
                    【解决方案17】:

                    只需控制+拖动栏项目到下面的功能。像魅力一样工作

                    @IBAction func done(sender: AnyObject) {
                        if((self.presentingViewController) != nil){
                            self.dismiss(animated: false, completion: nil)
                            print("done")
                        }
                    }
                    

                    【讨论】:

                      【解决方案18】:

                      如果你想拥有带返回箭头的返回按钮,你可以使用下面的图片和代码

                      backArrow.png backArrow@2x.png backArrow@3x.png

                      override func viewDidLoad() {
                          super.viewDidLoad()
                          let customBackButton = UIBarButtonItem(image: UIImage(named: "backArrow") , style: .plain, target: self, action: #selector(backAction(sender:)))
                          customBackButton.imageInsets = UIEdgeInsets(top: 2, left: -8, bottom: 0, right: 0)
                          navigationItem.leftBarButtonItem = customBackButton
                      }
                      
                      func backAction(sender: UIBarButtonItem) {
                          // custom actions here
                          navigationController?.popViewController(animated: true)
                      }
                      

                      【讨论】:

                        【解决方案19】:

                        斯威夫特 3:

                        override func didMove(toParentViewController parent: UIViewController?) {
                            super.didMove(toParentViewController: parent)
                        
                            if parent == nil{
                                print("Back button was clicked")
                            }
                        }
                        

                        【讨论】:

                        • -did/willMove(toParentViewController:) 可能比在 -viewWillDisappear 中检查 isMovingTfromParentViewController 更好,因为它仅在视图控制器实际更改父级时调用(而不是当视图被另一个 VC 的视图覆盖时)但更“正确”的解决方案是实现 UINavigationController 委托方法。不过要小心;如果 NavigationController 已经有一个委托,则您冒着剥夺其他委托的回调的风险。
                        • 我用 splitViewController 进行了测试。在那里,无法区分添加或删除。
                        【解决方案20】:

                        就我而言,viewWillDisappear 效果最好。但在某些情况下,必须修改以前的视图控制器。所以这是我的解决方案,可以访问 previous view controller,它适用于 Swift 4

                        override func viewWillDisappear(_ animated: Bool) {
                                super.viewWillDisappear(animated)
                                if isMovingFromParentViewController {
                                    if let viewControllers = self.navigationController?.viewControllers {
                                        if (viewControllers.count >= 1) {
                                            let previousViewController = viewControllers[viewControllers.count-1] as! NameOfDestinationViewController
                                            // whatever you want to do
                                            previousViewController.callOrModifySomething()
                                        }
                                    }
                                }
                            }
                        

                        【讨论】:

                        • -viewDidDisappear (或 -viewWillDisappear) 将被调用,即使视图被另一个视图控制器的视图覆盖(不仅仅是当
                        【解决方案21】:

                        这并不像我们一样难。只需为具有清晰背景颜色的 UIButton 创建一个框架,为按钮分配操作并放置在导航栏后退按钮上。最后在使用后取下按钮。

                        这是 Swift 3 使用 UIImage 而不是 UIButton 完成的示例代码

                        override func viewDidLoad() {
                            super.viewDidLoad()
                            let imageView = UIImageView()
                            imageView.backgroundColor = UIColor.clear
                            imageView.frame = CGRect(x:0,y:0,width:2*(self.navigationController?.navigationBar.bounds.height)!,height:(self.navigationController?.navigationBar.bounds.height)!)
                            let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(back(sender:)))
                            imageView.isUserInteractionEnabled = true
                            imageView.addGestureRecognizer(tapGestureRecognizer)
                            imageView.tag = 1
                            self.navigationController?.navigationBar.addSubview(imageView)
                            }
                        

                        编写需要执行的代码

                        func back(sender: UIBarButtonItem) {
                        
                            // Perform your custom actions}
                            _ = self.navigationController?.popViewController(animated: true)
                        
                            }
                        

                        执行操作后删除子视图

                        override func viewWillDisappear(_ animated: Bool) {
                            super.viewWillDisappear(animated)
                        
                            for view in (self.navigationController?.navigationBar.subviews)!{
                                if view.tag == 1 {
                                    view.removeFromSuperview()
                                }
                            }
                        

                        【讨论】:

                        • 谢谢老兄。 :-)
                        • 触地时如何创建状态?
                        • 这在 iOS 11 中似乎不起作用。当 UIImageView 的背景颜色清晰时则不起作用。将其设置为不同的颜色即可。
                        • 我们可以定义一个颜色清晰的 UIImageView,设置它的框架,分配轻触并放置在屏幕的任何位置。那我们为什么不能把它放在导航栏上。老实说,我不会推荐我写的东西。如果有问题肯定是有原因的,但这不是颜色问题。忘记代码遵循你会成功的逻辑。 :)
                        【解决方案22】:

                        override func willMove(toParentViewController parent: UIViewController?) { }

                        即使您将要在其中重写此方法的视图控制器 转到,它也会被调用。在其中检查“parent”是否为nil 不是确保将返回 移动到正确的UIViewController 的精确方法。要准确确定UINavigationController 是否正确导航回提供当前此版本的UIViewController,您需要遵守UINavigationControllerDelegate 协议。

                        注意:MyViewController 只是您想要检测返回的任何 UIViewController 的名称。

                        1) 在文件顶部添加UINavigationControllerDelegate

                        class MyViewController: UIViewController, UINavigationControllerDelegate {
                        

                        2) 将一个属性添加到您的类中,该属性将跟踪您正在从中进行的UIViewController

                        class MyViewController: UIViewController, UINavigationControllerDelegate {
                        
                        var previousViewController:UIViewController
                        

                        3) 在MyViewControllerviewDidLoad 方法中指定self 作为UINavigationController 的代表。

                        override func viewDidLoad() {
                            super.viewDidLoad()
                            self.navigationController?.delegate = self
                        }
                        

                        3) 在继续之前,将之前的UIViewController 分配为此属性。

                        // In previous UIViewController
                        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
                            if segue.identifier == "YourSegueID" {
                                if let nextViewController = segue.destination as? MyViewController {
                                    nextViewController.previousViewController = self
                                }
                            }
                        }
                        

                        4) 并符合UINavigationControllerDelegateMyViewController中的一种方法

                        func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
                            if viewController == self.previousViewController {
                                // You are going back
                            }
                        }
                        

                        【讨论】:

                        • 感谢您的帮助!读者注意将 UINavigationController 的委托设置为特定的视图控制器;如果导航控制器已经有一个委托,则您冒着剥夺其他委托的回调的风险。在我们的应用中,UINavigationController 的委托是一个共享对象(AppCoordinator),所有视图控制器都有一个指向它的指针。
                        【解决方案23】:

                        我创建了这个 (swift) 类来创建一个与常规按钮完全相同的后退按钮,包括后退箭头。它可以创建一个带有常规文本或图像的按钮。

                        用法

                        weak var weakSelf = self
                        
                        // Assign back button with back arrow and text (exactly like default back button)
                        navigationItem.leftBarButtonItems = CustomBackButton.createWithText("YourBackButtonTitle", color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))
                        
                        // Assign back button with back arrow and image
                        navigationItem.leftBarButtonItems = CustomBackButton.createWithImage(UIImage(named: "yourImageName")!, color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))
                        
                        func tappedBackButton() {
                        
                            // Do your thing
                        
                            self.navigationController!.popViewControllerAnimated(true)
                        }
                        

                        CustomBackButtonClass

                        (使用 Sketch & Paintcode 插件创建的绘制返回箭头的代码)

                        class CustomBackButton: NSObject {
                        
                            class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
                                let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
                                negativeSpacer.width = -8
                                let backArrowImage = imageOfBackArrow(color: color)
                                let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.Plain, target: target, action: action)
                                let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.Plain , target: target, action: action)
                                backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), forBarMetrics: UIBarMetrics.Default)
                                return [negativeSpacer, backArrowButton, backTextButton]
                            }
                        
                            class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
                                // recommended maximum image height 22 points (i.e. 22 @1x, 44 @2x, 66 @3x)
                                let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
                                negativeSpacer.width = -8
                                let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
                                let backImageView = UIImageView(image: image)
                                let customBarButton = UIButton(frame: CGRectMake(0,0,22 + backImageView.frame.width,22))
                                backImageView.frame = CGRectMake(22, 0, backImageView.frame.width, backImageView.frame.height)
                                customBarButton.addSubview(backArrowImageView)
                                customBarButton.addSubview(backImageView)
                                customBarButton.addTarget(target, action: action, forControlEvents: .TouchUpInside)
                                return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
                            }
                        
                            private class func drawBackArrow(frame frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
                                /// General Declarations
                                let context = UIGraphicsGetCurrentContext()!
                        
                                /// Resize To Frame
                                CGContextSaveGState(context)
                                let resizedFrame = resizing.apply(rect: CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
                                CGContextTranslateCTM(context, resizedFrame.minX, resizedFrame.minY)
                                let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
                                CGContextScaleCTM(context, resizedScale.width, resizedScale.height)
                        
                                /// Line
                                let line = UIBezierPath()
                                line.moveToPoint(CGPoint(x: 9, y: 9))
                                line.addLineToPoint(CGPoint.zero)
                                CGContextSaveGState(context)
                                CGContextTranslateCTM(context, 3, 11)
                                line.lineCapStyle = .Square
                                line.lineWidth = 3
                                color.setStroke()
                                line.stroke()
                                CGContextRestoreGState(context)
                        
                                /// Line Copy
                                let lineCopy = UIBezierPath()
                                lineCopy.moveToPoint(CGPoint(x: 9, y: 0))
                                lineCopy.addLineToPoint(CGPoint(x: 0, y: 9))
                                CGContextSaveGState(context)
                                CGContextTranslateCTM(context, 3, 2)
                                lineCopy.lineCapStyle = .Square
                                lineCopy.lineWidth = 3
                                color.setStroke()
                                lineCopy.stroke()
                                CGContextRestoreGState(context)
                        
                                CGContextRestoreGState(context)
                            }
                        
                            private class func imageOfBackArrow(size size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
                                var image: UIImage
                        
                                UIGraphicsBeginImageContextWithOptions(size, false, 0)
                                drawBackArrow(frame: CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
                                image = UIGraphicsGetImageFromCurrentImageContext()
                                UIGraphicsEndImageContext()
                        
                                return image
                            }
                        
                            private enum ResizingBehavior {
                                case AspectFit /// The content is proportionally resized to fit into the target rectangle.
                                case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
                                case Stretch /// The content is stretched to match the entire target rectangle.
                                case Center /// The content is centered in the target rectangle, but it is NOT resized.
                        
                                func apply(rect rect: CGRect, target: CGRect) -> CGRect {
                                    if rect == target || target == CGRect.zero {
                                        return rect
                                    }
                        
                                    var scales = CGSize.zero
                                    scales.width = abs(target.width / rect.width)
                                    scales.height = abs(target.height / rect.height)
                        
                                    switch self {
                                        case .AspectFit:
                                            scales.width = min(scales.width, scales.height)
                                            scales.height = scales.width
                                        case .AspectFill:
                                            scales.width = max(scales.width, scales.height)
                                            scales.height = scales.width
                                        case .Stretch:
                                            break
                                        case .Center:
                                            scales.width = 1
                                            scales.height = 1
                                    }
                        
                                    var result = rect.standardized
                                    result.size.width *= scales.width
                                    result.size.height *= scales.height
                                    result.origin.x = target.minX + (target.width - result.width) / 2
                                    result.origin.y = target.minY + (target.height - result.height) / 2
                                    return result
                                }
                            }
                        }
                        

                        SWIFT 3.0

                        class CustomBackButton: NSObject {
                        
                            class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
                                let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
                                negativeSpacer.width = -8
                                let backArrowImage = imageOfBackArrow(color: color)
                                let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.plain, target: target, action: action)
                                let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.plain , target: target, action: action)
                                backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), for: UIBarMetrics.default)
                                return [negativeSpacer, backArrowButton, backTextButton]
                            }
                        
                            class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
                                // recommended maximum image height 22 points (i.e. 22 @1x, 44 @2x, 66 @3x)
                                let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
                                negativeSpacer.width = -8
                                let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
                                let backImageView = UIImageView(image: image)
                                let customBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 22 + backImageView.frame.width, height: 22))
                                backImageView.frame = CGRect(x: 22, y: 0, width: backImageView.frame.width, height: backImageView.frame.height)
                                customBarButton.addSubview(backArrowImageView)
                                customBarButton.addSubview(backImageView)
                                customBarButton.addTarget(target, action: action, for: .touchUpInside)
                                return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
                            }
                        
                            private class func drawBackArrow(_ frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
                                /// General Declarations
                                let context = UIGraphicsGetCurrentContext()!
                        
                                /// Resize To Frame
                                context.saveGState()
                                let resizedFrame = resizing.apply(CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
                                context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
                                let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
                                context.scaleBy(x: resizedScale.width, y: resizedScale.height)
                        
                                /// Line
                                let line = UIBezierPath()
                                line.move(to: CGPoint(x: 9, y: 9))
                                line.addLine(to: CGPoint.zero)
                                context.saveGState()
                                context.translateBy(x: 3, y: 11)
                                line.lineCapStyle = .square
                                line.lineWidth = 3
                                color.setStroke()
                                line.stroke()
                                context.restoreGState()
                        
                                /// Line Copy
                                let lineCopy = UIBezierPath()
                                lineCopy.move(to: CGPoint(x: 9, y: 0))
                                lineCopy.addLine(to: CGPoint(x: 0, y: 9))
                                context.saveGState()
                                context.translateBy(x: 3, y: 2)
                                lineCopy.lineCapStyle = .square
                                lineCopy.lineWidth = 3
                                color.setStroke()
                                lineCopy.stroke()
                                context.restoreGState()
                        
                                context.restoreGState()
                            }
                        
                            private class func imageOfBackArrow(_ size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
                                var image: UIImage
                        
                                UIGraphicsBeginImageContextWithOptions(size, false, 0)
                                drawBackArrow(CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
                                image = UIGraphicsGetImageFromCurrentImageContext()!
                                UIGraphicsEndImageContext()
                        
                                return image
                            }
                        
                            private enum ResizingBehavior {
                                case AspectFit /// The content is proportionally resized to fit into the target rectangle.
                                case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
                                case Stretch /// The content is stretched to match the entire target rectangle.
                                case Center /// The content is centered in the target rectangle, but it is NOT resized.
                        
                                func apply(_ rect: CGRect, target: CGRect) -> CGRect {
                                    if rect == target || target == CGRect.zero {
                                        return rect
                                    }
                        
                                    var scales = CGSize.zero
                                    scales.width = abs(target.width / rect.width)
                                    scales.height = abs(target.height / rect.height)
                        
                                    switch self {
                                    case .AspectFit:
                                        scales.width = min(scales.width, scales.height)
                                        scales.height = scales.width
                                    case .AspectFill:
                                        scales.width = max(scales.width, scales.height)
                                        scales.height = scales.width
                                    case .Stretch:
                                        break
                                    case .Center:
                                        scales.width = 1
                                        scales.height = 1
                                    }
                        
                                    var result = rect.standardized
                                    result.size.width *= scales.width
                                    result.size.height *= scales.height
                                    result.origin.x = target.minX + (target.width - result.width) / 2
                                    result.origin.y = target.minY + (target.height - result.height) / 2
                                    return result
                                }
                            }
                        }
                        

                        【讨论】:

                        • 您愿意为 iOS 11 更新您的答案吗?
                        • 嗨@guido,您的解决方案非常完美,我尝试了您的代码并注意到即使您添加了负宽度的barbutton,后退按钮前面仍有空间。
                        【解决方案24】:
                            override public func viewDidLoad() {
                                 super.viewDidLoad()
                                 self.navigationController?.navigationBar.topItem?.title = GlobalVariables.selectedMainIconName
                                 let image = UIImage(named: "back-btn")
                        
                                 image = image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
                        
                                self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: UIBarButtonItemStyle.Plain, target: self, action: #selector(Current[enter image description here][1]ViewController.back) )
                            }
                        
                            func back() {
                              self.navigationController?.popToViewController( self.navigationController!.viewControllers[ self.navigationController!.viewControllers.count - 2 ], animated: true)
                            }
                        

                        【讨论】:

                          【解决方案25】:

                          一种选择是实现您自己的自定义后退按钮。您需要将以下代码添加到您的 viewDidLoad 方法中:

                          - (void) viewDidLoad {
                              [super viewDidLoad];
                              self.navigationItem.hidesBackButton = YES;
                              UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:self action:@selector(back:)];
                              self.navigationItem.leftBarButtonItem = newBackButton;
                          }
                          
                          - (void) back:(UIBarButtonItem *)sender {
                              // Perform your custom actions
                              // ...
                              // Go back to the previous ViewController
                              [self.navigationController popViewControllerAnimated:YES];
                          }
                          

                          更新:

                          这是 Swift 的版本:

                              override func viewDidLoad {
                                  super.viewDidLoad()
                                  self.navigationItem.hidesBackButton = true
                                  let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Bordered, target: self, action: "back:")
                                  self.navigationItem.leftBarButtonItem = newBackButton
                              }
                          
                              func back(sender: UIBarButtonItem) {
                                  // Perform your custom actions
                                  // ...
                                  // Go back to the previous ViewController
                                  self.navigationController?.popViewControllerAnimated(true)
                              }
                          

                          更新 2:

                          这是 Swift 3 的版本:

                              override func viewDidLoad {
                                  super.viewDidLoad()
                                  self.navigationItem.hidesBackButton = true
                                  let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.back(sender:)))
                                  self.navigationItem.leftBarButtonItem = newBackButton
                              }
                          
                              func back(sender: UIBarButtonItem) {
                                  // Perform your custom actions
                                  // ...
                                  // Go back to the previous ViewController
                                  _ = navigationController?.popViewController(animated: true)
                              }
                          

                          【讨论】:

                          • 这不会弹出到前一个视图控制器;它弹出到根视图控制器。
                          • 我怎样才能拥有像普通后退按钮一样的箭头?
                          • @TomSawyer 为此,请看下面的答案
                          • 替换系统按钮来覆盖功能不是一个好方法。最好的方法是下面的答案! stackoverflow.com/a/27715660/2307276
                          • 它的工作,但我们将失去滑动回来的行动
                          【解决方案26】:

                          据我了解,您想清空您的array,因为您按下后退按钮并弹出到您之前的@​​987654322@,您在此屏幕上加载的Array

                          let settingArray  = NSMutableArray()
                          @IBAction func Back(sender: AnyObject) {
                              self. settingArray.removeAllObjects()
                              self.dismissViewControllerAnimated(true, completion: nil)
                          } 
                          

                          【讨论】:

                            【解决方案27】:

                            如果你使用navigationController,则将UINavigationControllerDelegate 协议添加到类并添加委托方法,如下所示:

                            class ViewController:UINavigationControllerDelegate {
                            
                                func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController,
                            animated: Bool) {
                                    if viewController === self {
                                        // do here what you want
                                    }
                                }
                            }
                            

                            每当导航控制器滑动到新屏幕时都会调用此方法。如果按下后退按钮,则新的视图控制器是 ViewController 本身。

                            【讨论】:

                            • 当使用非 NSObjectProtocol 类作为委托时,这会变得很糟糕。
                            • 按下后退按钮时并不总是调用它。
                            【解决方案28】:

                            我通过调用/覆盖viewWillDisappear 然后像这样访问navigationController 的堆栈来实现这一点:

                            override func viewWillDisappear(animated: Bool) {
                                super.viewWillDisappear(animated)
                            
                                let stack = self.navigationController?.viewControllers.count
                            
                                if stack >= 2 {
                                    // for whatever reason, the last item on the stack is the TaskBuilderViewController (not self), so we only use -1 to access it
                                    if let lastitem = self.navigationController?.viewControllers[stack! - 1] as? theViewControllerYoureTryingToAccess {
                                        // hand over the data via public property or call a public method of theViewControllerYoureTryingToAccess, like
                                        lastitem.emptyArray()
                                        lastitem.value = 5
                                    }
                                }
                            }
                            

                            【讨论】:

                              【解决方案29】:

                              试试这个。

                              self.navigationItem.leftBarButtonItem?.target = "methodname"
                              func methodname ( ) {            
                                //    enter code here
                              }
                              

                              也试试这个。

                              override func viewWillAppear(animated: Bool) {
                                //empty your array
                              }
                              

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2012-12-24
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2021-11-12
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多