简体   繁体   English

按下 UINavigationController 的返回栏按钮时执行的操作

[英]Execute action when back bar button of UINavigationController is pressed

I need to execute an action (emptying an array), when the back button of a UINavigationController is pressed, while the button still causes the previous ViewController on the stack to appear.当按下UINavigationController的后退按钮时,我需要执行一个操作(清空数组),而该按钮仍然会导致堆栈上的前一个ViewController出现。 How could I accomplish this using swift?我如何使用 swift 完成此操作?在此处输入图像描述

Replacing the button to a custom one as suggested on another answer is possibly not a great idea as you will lose the default behavior and style.按照另一个答案的建议将按钮替换为自定义按钮可能不是一个好主意,因为您将失去默认行为和样式。

One other option you have is to implement the viewWillDisappear method on the View Controller and check for a property named isMovingFromParentViewController .另一种选择是在视图控制器上实现viewWillDisappear方法并检查名为isMovingFromParentViewController的属性。 If that property is true, it means the View Controller is disappearing because it's being removed (popped).如果该属性为真,则意味着视图控制器正在消失,因为它正在被移除(弹出)。

Should look something like:应该看起来像:

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

    if self.isMovingFromParentViewController {
        // Your code...
    }
}

In swift 4.2在快速 4.2

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

    if self.isMovingFromParent {
        // Your code...
    }
}

One option would be implementing your own custom back button.一种选择是实现您自己的自定义后退按钮。 You would need to add the following code to your viewDidLoad method:您需要将以下代码添加到您的 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];
}

UPDATE:更新:

Here is the version for Swift:这是 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)
    }

UPDATE 2:更新 2:

Here is the version for Swift 3:这是 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)
    }
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.")
    }
}

I was able to achieve this with the following :我能够通过以下方式实现这一目标:

Swift 3斯威夫特 3

override func didMoveToParentViewController(parent: UIViewController?) {
   super.didMoveToParentViewController(parent)

   if parent == nil {
      println("Back Button pressed.")
      delegate?.goingBack()
   }           
}

Swift 4斯威夫特 4

override func didMove(toParent parent: UIViewController?) {
    super.didMove(toParent: parent)

    if parent == nil {
        debugPrint("Back Button pressed.")
    }
}

No need of custom back button.无需自定义后退按钮。

I created this (swift) class to create a back button exactly like the regular one, including back arrow.我创建了这个 (swift) 类来创建一个与常规按钮完全相同的后退按钮,包括后退箭头。 It can create a button with regular text or with an image.它可以创建带有常规文本或图像的按钮。

Usage用法

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自定义返回按钮类

(code for drawing the back arrow created with Sketch & Paintcode plugin) (绘制使用 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斯威夫特 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
        }
    }
}

If you want to have back button with back arrow you can use an image and code below如果您想使用带有后退箭头的后退按钮,您可以使用下面的图片和代码

backArrow.png返回箭头.png 箭头 1 backArrow@2x.png backArrow@2x.png 箭头 2 backArrow@3x.png backArrow@3x.png 箭头 3

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

In Swift 5 and Xcode 10.2在 Swift 5 和 Xcode 10.2 中

Please don't add custom bar button item, use this default behaviour.请不要添加自定义栏按钮项,请使用此默认行为。

No need of viewWillDisappear , no need of custom BarButtonItem etc...不需要viewWillDisappear ,不需要自定义 BarButtonItem等...

It's better to detect when the VC is removed from it's parent.最好检测 VC 何时从其父级中删除。

Use any one of these two functions使用这两个函数中的任何一个

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

If you want stop default behaviour of back button then add custom BarButtonItem.如果您想停止后退按钮的默认行为,请添加自定义 BarButtonItem。

If you are using navigationController then add the UINavigationControllerDelegate protocol to class and add the delegate method as follows:如果您使用的是navigationController则将UINavigationControllerDelegate协议添加到类并添加委托方法,如下所示:

class ViewController:UINavigationControllerDelegate {

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

This method is called whenever the navigation controller will slide to a new screen.每当导航控制器滑动到新屏幕时都会调用此方法。 If the back button was pressed, the new view controller is ViewController itself.如果按下后退按钮,新的视图控制器就是ViewController本身。

NO

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

This will get called even if you are segueing to the view controller in which you are overriding this method.这将被调用,即使你是segueing视图控制器中你重写此方法。 In which check if the " parent " is nil of not is not a precise way to be sure of moving back to the correct UIViewController .其中检查“ parent ”是否nil不是确保移正确的UIViewController的精确方法。 To determine exactly if the UINavigationController is properly navigating back to the UIViewController that presented this current one, you will need to conform to the UINavigationControllerDelegate protocol.要准确确定UINavigationController是否正确导航回呈现当前的UIViewController ,您需要遵守UINavigationControllerDelegate协议。

YES是的

note: MyViewController is just the name of whatever UIViewController you want to detect going back from.注意: MyViewController只是您要检测返回的任何UIViewController的名称。

1) At the top of your file add UINavigationControllerDelegate . 1) 在文件顶部添加UINavigationControllerDelegate

class MyViewController: UIViewController, UINavigationControllerDelegate {

2) Add a property to your class that will keep track of the UIViewController that you are segueing from. 2)向您的类添加一个属性,该属性将跟踪您正在从中分离的UIViewController

class MyViewController: UIViewController, UINavigationControllerDelegate {

var previousViewController:UIViewController

3) in MyViewController 's viewDidLoad method assign self as the delegate for your UINavigationController . 3) 在MyViewControllerviewDidLoad方法中,将self指定为UINavigationController的委托。

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

3) Before you segue , assign the previous UIViewController as this property. 3)在segue 之前,将之前的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) And conform to one method in MyViewController of the UINavigationControllerDelegate 4) 并符合UINavigationControllerDelegate MyViewController中的一种方法

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

In my case the viewWillDisappear worked best.就我而言, viewWillDisappear效果最好。 But in some cases one has to modify the previous view controller.但在某些情况下,必须修改以前的视图控制器。 So here is my solution with access to the previous view controller and it works in Swift 4 :所以这是我可以访问以前的视图控制器的解决方案,它适用于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()
                }
            }
        }
    }

Before leave current controller I need to show alert.在离开当前控制器之前,我需要显示警报。 So I did it this way:所以我是这样做的:

  1. Add extention to UINavigationController with UINavigationBarDelegate使用UINavigationBarDelegateUINavigationController添加扩展
  2. Add selector to your controller navigationShouldPopOnBack(completion:)将选择器添加到您的控制器navigationShouldPopOnBack(completion:)

It's worked)成功了)

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

It's not difficult as we thing.我们的事情并不难。 Just create a frame for UIButton with clear background color, assign action for the button and place over the navigationbar back button.只需为 UIButton 创建一个具有清晰背景颜色的框架,为按钮分配操作并放置在导航栏后退按钮上。 And finally remove the button after use.最后在使用后取下按钮。

Here is the Swift 3 sample code done with UIImage instead of UIButton这是使用 UIImage 而不是 UIButton 完成的 Swift 3 示例代码

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

write the code need to be executed编写需要执行的代码

func back(sender: UIBarButtonItem) {

    // Perform your custom actions}
    _ = self.navigationController?.popViewController(animated: true)

    }

Remove the subView after action is performed执行操作后删除子视图

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

    for view in (self.navigationController?.navigationBar.subviews)!{
        if view.tag == 1 {
            view.removeFromSuperview()
        }
    }

For Swift 5 , we can check it in view will disappear对于Swift 5 ,我们可以在视图中检查它是否会消失

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

    if self.isMovingFromParent {
        delegate?.passValue(clickedImage: selectedImage)
    }
}

This is my solution这是我的解决方案

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

In your view controller, you can handle like this:在您的视图控制器中,您可以这样处理:

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

Swift 4.2:斯威夫特 4.2:

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

    if self.isMovingFromParent {
        // Your code...

    }
}

Swift 3:斯威夫特 3:

override func didMove(toParentViewController parent: UIViewController?) {
    super.didMove(toParentViewController: parent)

    if parent == nil{
        print("Back button was clicked")
    }
}

You can subclass UINavigationController and override popViewController(animated: Bool) .您可以popViewController(animated: Bool) UINavigationController并覆盖popViewController(animated: Bool) Beside being able to execute some code there you can also prevent the user from going back altogether, for instance to prompt to save or discard his current work.除了能够在那里执行一些代码之外,您还可以防止用户完全返回,例如提示保存或放弃他当前的工作。

Sample implementation where you can set a popHandler that gets set/cleared by pushed controllers.示例实现,您可以在其中设置由推送控制器设置/清除的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)
    }
}

And sample usage from a pushed controller that tracks unsaved work.以及来自跟踪未保存工作的推送控制器的示例用法。

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

As a nice touch, this will also get called by interactivePopGestureRecognizer when the user tries to go back using a swipe gesture.作为一个很好的接触,当用户尝试使用滑动手势返回时, interactivePopGestureRecognizer也会调用它。

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

just do control + drag the bar item to below func.只需控制 + 将栏项拖到 func 下方即可。 work like charm像魅力一样工作

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

在此处输入图片说明

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

    if self.isMovingToParent {

        //your code backView
    }
}

Swift 5 __ Xcode 11.5斯威夫特 5 __ Xcode 11.5

In my case I wanted to make an animation, and when it finished, go back.在我的例子中,我想制作一个动画,当它完成时,回去。 A way to overwrite the default action of the back button and call your custom action is this:覆盖后退按钮的默认操作并调用自定义操作的方法是:

     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 视图层次结构

Or you can use this method one time to explore the NavigationBar view hierarchy, and get the indexes to access to the _UIButtonBarButton view, cast to UIControl, remove the target-action, and add your custom targets-actions:或者您可以使用此方法一次探索 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)")
    }

Try this .尝试这个 。

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

Try on this too.也试试这个。

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

As I understand you want to empty your array as you press your back button and pop to your previous ViewController let your Array which you loaded on this screen is据我了解,当您按下后退按钮并弹出到之前的ViewController let您想清空arrayViewController let您在此屏幕上加载的Array

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

When back button is pressed, ignore interactive pop with screen edge gesture.当按下后退按钮时,忽略屏幕边缘手势的交互弹出。

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

I accomplished this by calling/overriding viewWillDisappear and then accessing the stack of the navigationController like this:我通过调用/覆盖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
        }
    }
}

You can do something in your Viewcontroller like你可以在你的 Viewcontroller 中做一些事情

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

For complete answer use Detecting when the 'back' button is pressed on a navbar要获得完整的答案,请使用检测何时在导航栏上按下“后退”按钮

Here is the simplest possible Swift 5 solution that doesn't require you to create a custom back button and give up all that UINavigationController left button functionality you get for free.这是最简单的 Swift 5 解决方案,它不需要您创建自定义后退按钮并放弃您免费获得的所有 UINavigationController 左按钮功能。

As Brandon A recommends above, you need need to implement UINavigationControllerDelegate in the view controller you want to interact with before returning to it.正如上面Brandon A 所建议的,在返回之前,您需要在要与之交互的视图控制器中实现UINavigationControllerDelegate A good way is to create an unwind segue that you can perform manually or automatically and reuse the same code from a custom done button or the back button.一个好方法是创建一个可以手动或自动执行的 unwind segue,并重用来自自定义完成按钮或后退按钮的相同代码。

First, make your view controller of interest (the one you want to detect returning to) a delegate of the navigation controller in its viewDidLoad :首先,让您感兴趣的视图控制器(您想要检测返回的视图控制器)成为其viewDidLoad导航控制器的委托:

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

Second, add an extension at the bottom of the file that overrides navigationController(willShow:animated:)其次,在文件底部添加一个覆盖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)
            }
        }
    }
}

Since your question included a UITableViewController , I included a way to get the index path of the row the user tapped.由于您的问题包括UITableViewController ,因此我提供了一种获取用户点击的行的索引路径的方法。

Try to call this 尝试称呼这个

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    if self.isMovingFromParent {
        print("isMovingFromParent()") // dismiss for push viewController
    }
    if self.isBeingDismissed {
        print("isBeingDismissed()") // dismiss for modal that doesn't has navigationController
    }
    if self.navigationController?.isBeingDismissed ?? false {
        print("navigationController?.isBeingDismissed()") // dismiss for modal that has navigationController
    }
}

My preference was to override the popViewController in the Navigation Controller.我的偏好是覆盖导航 Controller 中的 popViewController。 The advantages of this is:这样做的好处是:

  1. Your app keeps the default Back Button look and animations, and you don't have to manage it.您的应用会保留默认的后退按钮外观和动画,您无需对其进行管理。 This is particularly helpful if a user has Large Text set on their phone, since the default back button will increase or decrease in size based on the user settings.如果用户在手机上设置了大文本,这将特别有用,因为默认的后退按钮会根据用户设置增大或减小大小。
  2. You can stop the view from popping altogether, unlike using viewWillDisappear.与使用 viewWillDisappear 不同,您可以完全阻止视图弹出。

First, create a custom Navigation Controller class (and be sure to assign it to the Navigation Controller in your Story Board or wherever your navigation controller is created): First, create a custom Navigation Controller class (and be sure to assign it to the Navigation Controller in your Story Board or wherever your navigation controller is created):

class NavControllerWithBackButtonOverride: UINavigationController {

    var backButtonOverride: (() -> Void)? = nil

    override func popViewController(animated: Bool) -> UIViewController? {

        if backButtonOverride != nil {
            //if anything is assigned to the backButtonOverride the override will run
            self.backButtonOverride!()
            return nil
        } else {
            //otherwise the default popViewController will run
            return super.popViewController(animated: animated)
        }
    }
}

Then enable/disable the override in your View Controller by assigning a value to the backButtonOverride variable:然后通过为 backButtonOverride 变量赋值来启用/禁用 View Controller 中的覆盖:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.enableCustomBackButton()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    self.disableCustomBackButton()
}

/**
 Custom Back Button
 */

func customBackButtonAction() {
    print("DO THIS INSTEAD")
}

func enableCustomBackButton() {
    if let nav = self.navigationController as? NavControllerWithBackButtonOverride {
        nav.backButtonOverride = { self.customBackButtonAction() }
        nav.interactivePopGestureRecognizer?.isEnabled = false
    }    
}

func disableCustomBackButton() {
    if let nav = self.navigationController as? NavControllerWithBackButtonOverride {
    nav.backButtonOverride = nil
    nav.interactivePopGestureRecognizer?.isEnabled = true
    }
}

Note: I also disabled interactivePopGestureRecognizer because it was causing issues with the custom setup.注意:我还禁用了 interactivePopGestureRecognizer,因为它会导致自定义设置出现问题。

Swift 5+ (Back button with alert control) Swift 5+(带警报控件的后退按钮)

override func viewDidLoad() {
        super.viewDidLoad()
        
        self.navigationItem.hidesBackButton = true
        let newBackButton = UIBarButtonItem(title: "<Back", style: UIBarButtonItem.Style.plain, target: self, action: #selector(PGWebViewController.back(sender:)))
        self.navigationItem.leftBarButtonItem = newBackButton
}


@objc func back(sender: UIBarButtonItem) {
    
    let alert = UIAlertController(title: "Warning!", message: "Your payment process is not completed yet. Do you want to go back?", preferredStyle: .alert)
        
         let ok = UIAlertAction(title: "OK", style: .default, handler: { action in
             _ = self.navigationController?.popViewController(animated: true)
         })
         alert.addAction(ok)
         let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
         })
         alert.addAction(cancel)
         DispatchQueue.main.async(execute: {
            self.present(alert, animated: true)
    })}

This is how I solved it for my own problem这就是我为自己的问题解决的方法

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

}

You can simply remove unnecessary controllers from the stack, something like this:您可以简单地从堆栈中删除不必要的控制器,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在UINavigationController上按“后退按钮”时的自定义操作 - Custom action When Pressing 'back button' on a UINavigationController UINavigationController和后退按钮动作 - UINavigationController and back button action 当没有后退按钮时,向 UINavigationController 添加左栏按钮项 - Add a left bar button item to UINavigationController when no back button is present UINavigationController:确定是否按下了Back按钮或popViewControllerAnimated - UINavigationController: Determine if Back button was pressed or popViewControllerAnimated 单击UINavigationController的后退按钮时重新加载数据 - Reloading data when clicking back bar button of an UINavigationController 捕获UINavigationController的本机后退按钮操作 - Catching UINavigationController's native back button action 迅速按下导航栏后退按钮时重新加载视图控制器2 - Reloading view controller when navigation bar back button is pressed in swift 2 按下导航栏中的“后退”按钮时的自定义行为 - Custom behavior when “back” button in nav bar pressed 在 UINavigationController 中隐藏导航栏时不向后滑动 - No Swipe Back when hiding Navigation Bar in UINavigationController 检测是否按下导航栏中的默认后退按钮 - Detect if default back button in navigation bar is pressed
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM