我需要执行一个动作(清空一个数组),当UINavigationController的后退按钮被按下,而按钮仍然导致堆栈上的前一个ViewController出现。我如何使用swift来实现这一点?


当前回答

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

其他回答

一种选择是实现您自己的自定义后退按钮。你需要添加以下代码到你的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
        }

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

       @objc func back(sender: UIBarButtonItem) {
            // Perform your custom actions
            // ...
            // Go back to the previous ViewController
            _ = navigationController?.popViewController(animated: true)
        }

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

另一个选择是在视图控制器上实现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...
    }
}

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

你可以子类化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调用,当用户试图返回使用滑动手势。

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

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

首先,让你感兴趣的视图控制器(你想检测返回的那个)在它的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,所以我包含了一种获取用户点击行的索引路径的方法。