I can't override the show(_:sender:) method of UIViewController or else my app freezes when the method is called (Xcode 15.3 simulator, iOS 17.4, macOS Sonoma 14.3.1, MacBook Air M1 8GB).
Here is a table view that helps illustrate the UI freeze when you tap the right bar button item, which calls an overridden show(_:sender:) method:
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
}
}
As you can see, you may call an overridden present(_:animated:completion) (by tapping the left bar button item), but you may not call an overridden show(_:sender:).
Is there any workaround?
From the documentation for
UIViewController show(_:sender:):The problem with your code is that
targetViewController(forAction:sender:)is returningselfsince it isViewControllerthat is overridingshow(_:sender:). So you end up in an infinite recursive loop of calls toshow(_:sender:). You can see this if you put a breakpoint in your overriddenshow(_:sender:)and let the breakpoint be hit a couple of times. You can also add:and you will see "true" printed over and over.
When you override
show(_:sender:)you should actually perform the logic to show the given view controller instead of callingsuper.show(vc, sender: sender).Overriding
presentto callsuper.presentdoesn't have the same problem because the logic of the default implementation ofpresentdoesn't usetargetViewController(forAction:sender:)to find the implementor ofpresentlikeshowdoes.You should only override
show(_:sender:)in a custom container view controller class that needs to display the requested view controller is some specific way not handled by the normal framework.