我无法覆盖 UIViewController 的
show(_:sender:)
方法,否则我的应用程序在调用该方法时会冻结(Xcode 15.3 模拟器、iOS 17.4、macOS Sonoma 14.3.1、MacBook Air M1 8GB)。
这是一个表格视图,有助于说明当您点击右侧栏按钮项目时 UI 冻结,该项目调用重写的
show(_:sender:)
方法:
class ViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = .init(title: "Present", style: .plain, target: self, action: #selector(presentViewController))
navigationItem.rightBarButtonItem = .init(title: "Show", style: .done, target: self, action: #selector(showViewController))
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
@objc func presentViewController() {
present(AnotherViewController(), animated: true)
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
@objc func showViewController() {
show(AnotherViewController(), sender: self)
}
override func show(_ vc: UIViewController, sender: Any?) {
super.show(vc, sender: sender)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var contentConfiguration = cell.defaultContentConfiguration()
contentConfiguration.text = "Cell"
cell.contentConfiguration = contentConfiguration
return cell
}
}
class AnotherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
}
}
如您所见,您可以调用覆盖的
present(_:animated:completion)
(通过点击左侧栏按钮项),但您不能调用覆盖的 show(_:sender:)
。
有什么解决办法吗?
UIViewController show(_:sender:)
的文档:
此方法的默认实现调用
方法来定位视图控制器层次结构中覆盖此方法的对象。然后,它调用该目标对象上的方法,该方法以适当的方式显示视图控制器。如果targetViewController(forAction:sender:)
方法返回 nil,则该方法使用窗口的根视图控制器以模态方式呈现 vc。targetViewController(forAction:sender:)
您的代码的问题是
targetViewController(forAction:sender:)
正在返回self
,因为它是ViewController
覆盖了show(_:sender:)
。因此,您最终会陷入无限递归循环中调用 show(_:sender:)
。如果您在覆盖的 show(_:sender:)
中放置一个断点并让该断点被击中几次,您就可以看到这一点。您还可以添加:
override func targetViewController(forAction action: Selector, sender: Any?) -> UIViewController? {
let res = super.targetViewController(forAction: action, sender: sender)
print(res === self)
return res
}
你会看到“true”被一遍又一遍地打印。
当您重写
show(_:sender:)
时,您实际上应该执行逻辑来显示给定的视图控制器,而不是调用 super.show(vc, sender: sender)
。
重写
present
来调用 super.present
不会有同样的问题,因为 present
默认实现的逻辑不会像 targetViewController(forAction:sender:)
那样使用 present
来查找 show
的实现者。
您应该只在需要显示请求的视图控制器的自定义容器视图控制器类中重写
show(_:sender:)
,这是普通框架无法处理的某种特定方式。