You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 09:17:06