iOS Swift:ScriptMessageHandler调用函数隐藏导航栏失效问题
问题解决:ScriptMessageHandler触发函数无法修改导航栏
问题场景
在Swift iOS应用中,通过WebView的ScriptMessageHandler接收JavaScript消息触发hideIt()函数隐藏导航栏时,函数虽被调用但所有navigationController相关值均为nil,功能失效;但通过原生按钮触发同一函数时,navigationController正常,导航栏能成功隐藏。
核心代码示例
VisitableViewController代码
import Turbo import UIKit class VisitableViewController: Turbo.VisitableViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .background // 添加测试按钮 let hideShowButton: UIButton = { let button = UIButton() button.setTitle("Hide Nav", for: .normal) button.titleLabel?.tintColor = .white button.titleLabel?.font = .boldSystemFont(ofSize: 10) button.backgroundColor = .background_blue_900 button.contentEdgeInsets = .init(top: 7, left: 10, bottom: 7, right: 10) button.layer.cornerRadius = 14 button.addTarget(self, action: #selector(self.hideIt), for: .touchUpInside) return button }() hideShowButton.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(hideShowButton) hideShowButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true hideShowButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true } @objc func hideIt() { print("***start") visitableViewController.navigationController?.setNavigationBarHidden(true, animated: true) print("navigationController:") print(navigationController) print("self.navigationController:") print(self.navigationController) print("navigationController?.visibleViewController") print(navigationController?.visibleViewController) print("navigationController?.visibleViewController?.navigationItem.title:") print(navigationController?.visibleViewController?.navigationItem.title) print("**end") } }
ScriptMessageHandler代码
import WebKit protocol ScriptMessageDelegate: AnyObject { func signOut() func goBack() } class ScriptMessageHandler: NSObject, WKScriptMessageHandler { private weak var delegate: ScriptMessageDelegate? init(delegate: ScriptMessageDelegate) { self.delegate = delegate } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let body = message.body as? [String: Any], let message = ScriptMessage(body: body) else { return } print(message) routeMessage(message) } private func routeMessage(_ message: ScriptMessage) { switch message { case .signOut: delegate?.signOut() case .goBack: print("*********Run hideIt() in VisitableViewController when receiving goBack Javascript message") let visitable = VisitableViewController(); visitable.hideIt() } } }
控制台输出对比
JavaScript消息触发时
***start VisitableViewController(): <Myapp.VisitableViewController: 0x103240c10> navigationController: nil self.navigationController: nil navigationController?.visibleViewController nil navigationController?.visibleViewController?.navigationItem.title: nil **end
原生按钮触发时
***start VisitableViewController(): <Myapp.VisitableViewController: 0x10335f4b0> navigationController: Optional(<UINavigationController: 0x104027000>) self.navigationController: Optional(<UINavigationController: 0x104027000>) navigationController?.visibleViewController Optional(<Jumpstart.VisitableViewController: 0x1032442e0>) navigationController?.visibleViewController?.navigationItem.title: Optional("My App Name") **end
问题原因
在ScriptMessageHandler的routeMessage方法中,新建了一个VisitableViewController实例,这个新实例从未被加入到导航栈中,因此它的navigationController属性为nil,和当前屏幕上显示的控制器不是同一个对象,自然无法修改导航栏。
解决方案
步骤1:让VisitableViewController遵守ScriptMessageDelegate协议
修改VisitableViewController,实现协议中的方法,并在viewDidLoad中初始化ScriptMessageHandler时传入自身作为代理:
import Turbo import UIKit class VisitableViewController: Turbo.VisitableViewController, ScriptMessageDelegate { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .background // 初始化ScriptMessageHandler,传入当前控制器作为代理 let scriptHandler = ScriptMessageHandler(delegate: self) webView.configuration.userContentController.add(scriptHandler, name: "yourMessageHandlerName") // 原按钮代码保持不变 let hideShowButton: UIButton = { let button = UIButton() button.setTitle("Hide Nav", for: .normal) button.titleLabel?.tintColor = .white button.titleLabel?.font = .boldSystemFont(ofSize: 10) button.backgroundColor = .background_blue_900 button.contentEdgeInsets = .init(top: 7, left: 10, bottom: 7, right: 10) button.layer.cornerRadius = 14 button.addTarget(self, action: #selector(self.hideIt), for: .touchUpInside) return button }() hideShowButton.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(hideShowButton) hideShowButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true hideShowButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true } // 实现ScriptMessageDelegate协议方法 func signOut() { // 这里添加登出逻辑 } func goBack() { self.hideIt() // 调用当前实例的hideIt方法 } @objc func hideIt() { print("***start") self.navigationController?.setNavigationBarHidden(true, animated: true) print("navigationController:") print(navigationController) print("self.navigationController:") print(self.navigationController) print("navigationController?.visibleViewController") print(navigationController?.visibleViewController) print("navigationController?.visibleViewController?.navigationItem.title:") print(navigationController?.visibleViewController?.navigationItem.title) print("**end") } }
步骤2:修改ScriptMessageHandler的routeMessage方法
去掉新建实例的代码,改为通过代理调用当前控制器的方法:
private func routeMessage(_ message: ScriptMessage) { switch message { case .signOut: delegate?.signOut() case .goBack: print("*********触发VisitableViewController的hideIt()方法") delegate?.goBack() // 通过代理调用当前显示的控制器方法 } }
原理说明
修改后,ScriptMessageHandler持有的是当前已经在导航栈中的VisitableViewController实例的弱引用,调用的是该实例的hideIt()方法,此时self.navigationController是有效的,能够正常修改导航栏状态。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

